<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN""http://www.w3.org/TR/html4/loose.dtd">
<HTML
><HEAD
><TITLE
>Mantis Bug Tracker Developers Guide</TITLE
><META
NAME="GENERATOR"
CONTENT="Modular DocBook HTML Stylesheet Version 1.79"></HEAD
><BODY
CLASS="BOOK"
BGCOLOR="#FFFFFF"
TEXT="#000000"
LINK="#0000FF"
VLINK="#840084"
ALINK="#0000FF"
><DIV
CLASS="BOOK"
><A
NAME="INDEX"
></A
><DIV
CLASS="TITLEPAGE"
><H1
CLASS="TITLE"
><A
NAME="INDEX"
>Mantis Bug Tracker Developers Guide</A
></H1
><P
CLASS="COPYRIGHT"
>Copyright &copy; 2010 The MantisBT Team</P
><DIV
><DIV
CLASS="ABSTRACT"
><P
></P
><A
NAME="AEN7"
></A
><P
>A reference guide and documentation of the Mantis Bug Tracker for developers and community members.</P
><P
>Build Date: 23 April 2010</P
><P
></P
></DIV
></DIV
><DIV
CLASS="LEGALNOTICE"
><P
></P
><A
NAME="LEGAL"
></A
><P
>			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.
			</P
><P
></P
></DIV
><HR></DIV
><DIV
CLASS="TOC"
><DL
><DT
><B
>Table of Contents</B
></DT
><DT
>1. <A
HREF="#DEV.CONTRIB"
>Contributing to MantisBT</A
></DT
><DD
><DL
><DT
>1.1. <A
HREF="#DEV.CONTRIB.SETUP"
>Initial Setup</A
></DT
><DT
>1.2. <A
HREF="#DEV.CONTRIB.CLONE"
>Cloning the Repository</A
></DT
><DT
>1.3. <A
HREF="#DEV.CONTRIB.BRANCH"
>Maintaining Tracking Branches</A
></DT
><DT
>1.4. <A
HREF="#DEV.CONTRIB.PREPARE"
>Preparing Feature Branches</A
></DT
><DD
><DL
><DT
>1.4.1. <A
HREF="#DEV.CONTRIB.PREPARE.LOCAL"
>Private Branches</A
></DT
><DT
>1.4.2. <A
HREF="#DEV.CONTRIB.PREPARE.PUBLIC"
>Public Branches</A
></DT
></DL
></DD
><DT
>1.5. <A
HREF="#DEV.CONTRIB.TEST"
>Running PHPUnit tests</A
></DT
><DT
>1.6. <A
HREF="#DEV.CONTRIB.SUBMIT"
>Submitting Changes</A
></DT
><DD
><DL
><DT
>1.6.1. <A
HREF="#DEV.CONTRIB.SUBMIT.PATCH"
>Via Formatted Patches</A
></DT
><DT
>1.6.2. <A
HREF="#DEV.CONTRIB.SUBMIT.REPO"
>Via Public Repository</A
></DT
></DL
></DD
></DL
></DD
><DT
>2. <A
HREF="#DEV.DATABASE"
>Database Schema Management</A
></DT
><DD
><DL
><DT
>2.1. <A
HREF="#DEV.DATABASE.SCHEMA"
>Schema Definition</A
></DT
><DT
>2.2. <A
HREF="#DEV.DATABASE.INSTALL"
>Installation / Upgrade Process</A
></DT
></DL
></DD
><DT
>3. <A
HREF="#DEV.EVENTS"
>Event System</A
></DT
><DD
><DL
><DT
>3.1. <A
HREF="#DEV.EVENTS.CONCEPTS"
>General Concepts</A
></DT
><DT
>3.2. <A
HREF="#DEV.EVENTS.API"
>API Usage</A
></DT
><DT
>3.3. <A
HREF="#DEV.EVENTS.TYPES"
>Event Types</A
></DT
></DL
></DD
><DT
>4. <A
HREF="#DEV.PLUGINS"
>Plugin System</A
></DT
><DD
><DL
><DT
>4.1. <A
HREF="#DEV.PLUGINS.CONCEPTS"
>General Concepts</A
></DT
><DT
>4.2. <A
HREF="#DEV.PLUGINS.BUILDING"
>Building a Plugin</A
></DT
><DD
><DL
><DT
>4.2.1. <A
HREF="#DEV.PLUGINS.BUILDING.BASICS"
>The Basics</A
></DT
><DT
>4.2.2. <A
HREF="#DEV.PLUGINS.BUILDING.PAGES"
>Pages and Files</A
></DT
><DT
>4.2.3. <A
HREF="#DEV.PLUGINS.BUILDING.EVENTS"
>Events</A
></DT
><DT
>4.2.4. <A
HREF="#DEV.PLUGINS.BUILDING.CONFIG"
>Configuration</A
></DT
><DT
>4.2.5. <A
HREF="#DEV.PLUGINS.BUILDING.LANGUAGE"
>Language and Localization</A
></DT
></DL
></DD
><DT
>4.3. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE"
>Example Plugin Source Listing</A
></DT
><DD
><DL
><DT
>4.3.1. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE.EXAMPLE.PHP"
>Example/Example.php</A
></DT
><DT
>4.3.2. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE.FOO.CSS"
>Example/files/foo.css</A
></DT
><DT
>4.3.3. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE.STRINGSENGLISH.TXT"
>Example/lang/strings_english.txt</A
></DT
><DT
>4.3.4. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE.CONFIGPAGE.PHP"
>Example/page/config_page.php</A
></DT
><DT
>4.3.5. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE.CONFIGUPDATE.PHP"
>Example/pages/config_update.php</A
></DT
><DT
>4.3.6. <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE.FOO.PHP"
>Example/page/foo.php</A
></DT
></DL
></DD
><DT
>4.4. <A
HREF="#DEV.PLUGINS.API"
>API Usage</A
></DT
></DL
></DD
><DT
>5. <A
HREF="#DEV.EVENTREF"
>Event Reference</A
></DT
><DD
><DL
><DT
>5.1. <A
HREF="#DEV.EVENTREF.INTRO"
>Introduction</A
></DT
><DT
>5.2. <A
HREF="#DEV.EVENTREF.SYSTEM"
>System Events</A
></DT
><DT
>5.3. <A
HREF="#DEV.EVENTREF.OUTPUT"
>Output Modifier Events</A
></DT
><DD
><DL
><DT
>5.3.1. <A
HREF="#DEV.EVENTREF.OUTPUT.DISPLAY"
>String Display</A
></DT
><DT
>5.3.2. <A
HREF="#DEV.EVENTREF.OUTPUT.MENU"
>Menu Items</A
></DT
><DT
>5.3.3. <A
HREF="#DEV.EVENTREF.OUTPUT.LAYOUT"
>Page Layout</A
></DT
></DL
></DD
><DT
>5.4. <A
HREF="#DEV.EVENTREF.FILTER"
>Bug Filter Events</A
></DT
><DD
><DL
><DT
>5.4.1. <A
HREF="#DEV.EVENTREF.FILTER.CUSTOM"
>Custom Filters and Columns</A
></DT
></DL
></DD
><DT
>5.5. <A
HREF="#DEV.EVENTREF.BUG"
>Bug and Bugnote Events</A
></DT
><DD
><DL
><DT
>5.5.1. <A
HREF="#DEV.EVENTREF.BUG.VIEW"
>Bug View</A
></DT
><DT
>5.5.2. <A
HREF="#DEV.EVENTREF.BUG.ACTION"
>Bug Actions</A
></DT
><DT
>5.5.3. <A
HREF="#DEV.EVENTREF.BUG.NOTEVIEW"
>Bugnote View</A
></DT
><DT
>5.5.4. <A
HREF="#DEV.EVENTREF.BUG.NOTEACTION"
>Bugnote Actions</A
></DT
></DL
></DD
><DT
>5.6. <A
HREF="#DEV.EVENTREF.NOTIFY"
>Notification Events</A
></DT
><DD
><DL
><DT
>5.6.1. <A
HREF="#DEV.EVENTREF.NOTIFY.USER"
>Recipient Selection</A
></DT
></DL
></DD
><DT
>5.7. <A
HREF="#DEV.EVENTREF.ACCOUNT"
>User Account Events</A
></DT
><DD
><DL
><DT
>5.7.1. <A
HREF="#DEV.EVENTREF.ACCOUNT.PREFS"
>Account Preferences</A
></DT
></DL
></DD
><DT
>5.8. <A
HREF="#DEV.EVENTREF.MANAGE"
>Management Events</A
></DT
><DD
><DL
><DT
>5.8.1. <A
HREF="#DEV.EVENTREF.MANAGE.PROJECT"
>Projects and Versions</A
></DT
></DL
></DD
></DL
></DD
><DT
>6. <A
HREF="#DEV.APPENDIX"
>Appendix</A
></DT
><DD
><DL
><DT
>6.1. <A
HREF="#DEV.APPENDIX.GIT"
>Git References</A
></DT
></DL
></DD
></DL
></DIV
><DIV
CLASS="CHAPTER"
><HR><H1
><A
NAME="DEV.CONTRIB"
></A
>Chapter 1. Contributing to MantisBT</H1
><P
>		MantisBT uses the source control tool <A
HREF="http://git.or.cz"
TARGET="_top"
>Git</A
>
		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
		<A
HREF="#DEV.APPENDIX.GIT"
>Appendix</A
>.
	</P
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.CONTRIB.SETUP"
>1.1. Initial Setup</A
></H2
><P
>			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).
		</P
><P
>			To set up your name and email address with Git, run the following
			commands, substituting your own real name and email address:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git config --global user.name "John Smith"
$ git config --global user.email "jsmith@mantisbt.org"
		</PRE
><P
>			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:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ 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"
		</PRE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.CONTRIB.CLONE"
>1.2. Cloning the Repository</A
></H2
><P
>			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
			<A
HREF="git://mantisbt.org/mantisbt.git"
TARGET="_top"
>git://mantisbt.org/mantisbt.git</A
>.
			To clone the repository, perform the following from your target workspace:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git clone git://mantisbt.org/mantisbt.git
		</PRE
><P
>			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 <SPAN
CLASS="emphasis"
><I
CLASS="EMPHASIS"
>will fail</I
></SPAN
> if you do not have developer access
			or do not have your public SSH key set up correctly.
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git clone git@mantisbt.org:mantisbt.git
		</PRE
><P
>			After performing the cloning operation, you should end up with a new
			directory in your workspace, <TT
CLASS="FILENAME"
>mantisbt/</TT
>.  By default,
			it will only track code from the primary remote branch, <TT
CLASS="LITERAL"
>master</TT
>,
			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, <TT
CLASS="LITERAL"
>master-1.2.x</TT
>.
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout -b master-1.2.x origin/master-1.2.x
		</PRE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.CONTRIB.BRANCH"
>1.3. Maintaining Tracking Branches</A
></H2
><P
>			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 <TT
CLASS="LITERAL"
>master</TT
> and
			<TT
CLASS="LITERAL"
>master-1.2.x</TT
>.
		</P
><P
>			First, you'll need to got the latest information from the remote repo:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git fetch origin
		</PRE
><P
>			Then for each tracking branch you have, enter the following commands:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout master
$ git rebase origin/master
		</PRE
><P
>			Alternatively, you may combine the above steps into a single command
			for each remote tracking branch:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout master
$ git pull --rebase
		</PRE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.CONTRIB.PREPARE"
>1.4. Preparing Feature Branches</A
></H2
><P
>			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.  <SPAN
CLASS="emphasis"
><I
CLASS="EMPHASIS"
>Both methods assume that you have already
			performed the previous step, to <A
HREF="#DEV.CONTRIB.BRANCH"
>update
			your local tracking branches</A
>.</I
></SPAN
>
		</P
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.CONTRIB.PREPARE.LOCAL"
>1.4.1. Private Branches</A
></H3
><P
>				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 <TT
CLASS="LITERAL"
>master</TT
> is to use the <B
CLASS="COMMAND"
>rebase</B
>
				command.  This will append all of your feature branch commits into a linear
				history after the last commit on the <TT
CLASS="LITERAL"
>master</TT
> branch.
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout feature
$ git rebase master
			</PRE
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout feature
$ git fetch remote/feature
$ git rebase remote/feature
			</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.CONTRIB.PREPARE.PUBLIC"
>1.4.2. Public Branches</A
></H3
><P
>				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
				<TT
CLASS="LITERAL"
>master</TT
>.
			</P
><P
>				To bring public branch up to date, you'll need to <B
CLASS="COMMAND"
>merge</B
>
				the current <TT
CLASS="LITERAL"
>master</TT
> 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 <A
HREF="#DEV.CONTRIB.SUBMIT.REPO"
>submission process</A
>.
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout feature
$ git merge master
			</PRE
><P
>				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.
			</P
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.CONTRIB.TEST"
>1.5. Running PHPUnit tests</A
></H2
><P
>			MantisBT has a suite of PHPUnit tests found in the <TT
CLASS="LITERAL"
>tests</TT
>
			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.
		</P
><P
>			In order to run the tests, you will need to have the PHP Soap extension
			and <A
HREF="http://www.phpunit.de"
TARGET="_top"
>PHPUnit</A
> installed. The tests
			are configured using a <TT
CLASS="LITERAL"
>bootstrap.php</TT
> file. The 
			<TT
CLASS="LITERAL"
>boostrap.php.sample</TT
> file contains the settings you will
			need to adjust to run all the tests. 
		</P
><P
>			Running the unit tests is done from the <TT
CLASS="LITERAL"
>tests</TT
> directory
			using the following command:
		</P
><PRE
CLASS="PROGRAMLISTING"
>$ phpunit --bootstrap bootstrap.php AllTests.php
		</PRE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.CONTRIB.SUBMIT"
>1.6. Submitting Changes</A
></H2
><P
>			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.
		</P
><P
>			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.
		</P
><P
>			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
			<A
HREF="http://git.mantisforge.org"
TARGET="_top"
>MantisForge.org</A
>,
			<A
HREF="http://github.com"
TARGET="_top"
>GitHub</A
>, or
			<A
HREF="http://gitorious.com"
TARGET="_top"
>Gitorious</A
>.
		</P
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.CONTRIB.SUBMIT.PATCH"
>1.6.1. Via Formatted Patches</A
></H3
><P
>				Assuming that you have an existing local branch that you've kept up to date
				with <TT
CLASS="LITERAL"
>master</TT
> as described in
				<A
HREF="#DEV.CONTRIB.PREPARE"
>Preparing Feature Branches</A
>,
				generating a formatted patch set should be relatively straightforward,
				using an appropriate filename as the target of the patch set:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git format-patch --binary --stdout origin/master..HEAD &#62; feature_branch.patch
			</PRE
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git am --signoff feature_branch.patch
			</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.CONTRIB.SUBMIT.REPO"
>1.6.2. Via Public Repository</A
></H3
><P
>				We'll assume that you've already set up a public repository, either on a
				free repository hosting site, or using <B
CLASS="COMMAND"
>git-daemon</B
> on your own
				machine, and that you know both the public clone URL and the private push
				URL for your public repository.
			</P
><P
>				For the purpose of this demonstration, we'll use a public clone URL of
				<TT
CLASS="LITERAL"
>git://mantisbt.org/contrib.git</TT
>, a private push URL of
				<TT
CLASS="LITERAL"
>git@mantisbt.org:contrib.git</TT
>, and a hypothetical
				topic branch named <TT
CLASS="LITERAL"
>feature</TT
>.
			</P
><P
>				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 <TT
CLASS="LITERAL"
>public</TT
> for this; remember to
				replace the URL's and branch name as appropriate:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git remote add public git@mantisbt.org:contrib.git
$ git push public feature
			</PRE
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git request-pull origin/master git://mantisbt.org/contrib.git feature
			</PRE
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git remote add feature git://mantisbt.org/contrib.git
$ git checkout -b feature feature/feature
			</PRE
><P
>				If a remote branch is approved for entry into <TT
CLASS="LITERAL"
>master</TT
>,
				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 <TT
CLASS="LITERAL"
>master</TT
>:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout feature
$ git rebase master
$ git checkout master
$ git merge --ff feature
			</PRE
><P
><SPAN
CLASS="emphasis"
><I
CLASS="EMPHASIS"
>				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:
			</I
></SPAN
></P
><PRE
CLASS="PROGRAMLISTING"
>$ git checkout feature
$ git rebase master
$ git format-patch --binary --stdout master..HEAD &#62; feature_branch.patch
$ git am --signoff feature_branch.patch
			</PRE
></DIV
></DIV
></DIV
><DIV
CLASS="CHAPTER"
><HR><H1
><A
NAME="DEV.DATABASE"
></A
>Chapter 2. Database Schema Management</H1
><DIV
CLASS="SECT1"
><H2
CLASS="SECT1"
><A
NAME="DEV.DATABASE.SCHEMA"
>2.1. Schema Definition</A
></H2
><P
>			TODO: Discuss the ADODB datadict formats and the format MantisBT expects for schema deinitions.
		</P
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.DATABASE.INSTALL"
>2.2. Installation / Upgrade Process</A
></H2
><P
>			TODO: Discuss how MantisBT handles a database installation / upgrade, including the use of the config system and schema definitions.
		</P
></DIV
></DIV
><DIV
CLASS="CHAPTER"
><HR><H1
><A
NAME="DEV.EVENTS"
></A
>Chapter 3. Event System</H1
><DIV
CLASS="SECT1"
><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTS.CONCEPTS"
>3.1. General Concepts</A
></H2
><P
>			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.
		</P
><P
>			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.
		</P
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTS.API"
>3.2. API Usage</A
></H2
><P
>			This is a general overview of the event API.  For more detailed analysis,
			you may reference the file <TT
CLASS="FILENAME"
>core/event_api.php</TT
> in the
			codebase.
		</P
><A
NAME="DEV.EVENTS.API.DECLARE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>Declaring Events</B
></P
><P
>				When declaring events, the only information needed is the event name
				and event type.  Events can be declared alone using the form:
			</P
><PRE
CLASS="PROGRAMLISTING"
>event_declare( $name, $type=EVENT_TYPE_DEFAULT );</PRE
><P
>				or they can be declared in groups using key/value pairs of name =&#62; type
				relations, stored in a single array, such as:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$events = array(
	$name_1 =&#62; $type_1,
	$name_2 =&#62; $type_2,
	...
	);

event_declare_many( $events );
			</PRE
></BLOCKQUOTE
><A
NAME="DEV.EVENTS.API.HOOK"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>Hooking Events</B
></P
><P
>				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.
			</P
><PRE
CLASS="PROGRAMLISTING"
>event_hook( $event_name, $callback, [$plugin] );</PRE
><P
>				In order to hook many functions at once, using key/value pairs of name
				=&#62; callback relations, in a single array:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$events = array(
	$event_1 =&#62; $callback_1,
	$event_2 =&#62; $callback_2,
	...
	);

event_hook( $events, [$plugin] );
			</PRE
></BLOCKQUOTE
><A
NAME="DEV.EVENTS.API.SIGNAL"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>Signalling Events</B
></P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$value = event_signal( $event_name, [ array( $param, ... ), [ array( $static_param, ... ) ] ] );</PRE
><P
>				Each type of event (and individual events themselves) will use different
				combinations of parameters and return values, so use of the
				<A
HREF="#DEV.EVENTREF"
>Event Reference</A
> is reccommended for
				determining the unique needs of each event when signalling and hooking them.
			</P
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTS.TYPES"
>3.3. Event Types</A
></H2
><P
>			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.
		</P
><P
>			Each type of event (and individual events themselves) will use different
			combinations of parameters and return values, so use of the
			<A
HREF="#DEV.EVENTREF"
>Event Reference</A
> is reccommended for
			determining the unique needs of each event when signalling and hooking them.
		</P
><A
NAME="DEV.EVENTS.TYPES.EXECUTE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TYPE_EXECUTE</B
></P
><P
>				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.
			</P
><P
>				These events only use the first parameter array, and return values from
				hooked functions are ignored.  Example usage:
			</P
><PRE
CLASS="PROGRAMLISTING"
>event_signal( $event_name, [ array( $param, ... ) ] );</PRE
></BLOCKQUOTE
><A
NAME="DEV.EVENTS.TYPES.OUTPUT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TYPE_OUTPUT</B
></P
><P
>				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.
			</P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>event_signal( $event_name, [ array( $param, ... ) ] );</PRE
></BLOCKQUOTE
><A
NAME="DEV.EVENTS.TYPES.CHAIN"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TYPE_CHAIN</B
></P
><P
>				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.
			</P
><P
>				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.
			</P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$value = event_signal( $event_name, $param, [ array( $static_param, ... ) ] );</PRE
></BLOCKQUOTE
><A
NAME="DEV.EVENTS.TYPES.FIRST"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TYPE_FIRST</B
></P
><P
>				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.
			</P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$value = event_signal( $event_name, [ array( $param, ... ) ] );</PRE
></BLOCKQUOTE
><A
NAME="DEV.EVENTS.TYPES.DEFAULT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TYPE_DEFAULT</B
></P
><P
>				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.
			</P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
>$values = event_signal( $event_name, [ array( $param, ... ) ] );</PRE
></BLOCKQUOTE
></DIV
></DIV
><DIV
CLASS="CHAPTER"
><HR><H1
><A
NAME="DEV.PLUGINS"
></A
>Chapter 4. Plugin System</H1
><DIV
CLASS="SECT1"
><H2
CLASS="SECT1"
><A
NAME="DEV.PLUGINS.CONCEPTS"
>4.1. General Concepts</A
></H2
><P
>			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
			<A
HREF="#DEV.EVENTS"
>Event System</A
> to offer developers rapid
			creation and testing of extensions, without the need to modify core files.
		</P
><P
>			Plugins are defined as implementations, or subclasses, of the
			<CODE
CLASS="CLASSNAME"
>MantisPlugin</CODE
> class as defined in
			<TT
CLASS="FILENAME"
>core/classes/MantisPlugin.php</TT
>.  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
			<CODE
CLASS="CLASSNAME"
>MantisPlugin</CODE
> 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.
		</P
><P
>			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.
		</P
><P
>			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.
		</P
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.PLUGINS.BUILDING"
>4.2. Building a Plugin</A
></H2
><P
>			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.
		</P
><P
>			This walkthrough will be working towards building a single end result: the
			"Example" plugin as listed in the <A
HREF="#DEV.PLUGINS.BUILDING.SOURCE"
>			next section</A
>.  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.
		</P
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.BASICS"
>4.2.1. The Basics</A
></H3
><P
>				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.
			</P
><DIV
CLASS="SECT3"
><HR><H4
CLASS="SECT3"
><A
NAME="DEV.PLUGINS.BUILDING.BASICS.STRUCTURE"
>4.2.1.1. Plugin Structure</A
></H4
><P
>					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".
				</P
><P
>					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:
				</P
><PRE
CLASS="PROGRAMLISTING"
>Example/
	Example.php
				</PRE
><P
>					This top-level PHP file must then contain a concrete class deriving from
					the <CODE
CLASS="CLASSNAME"
>MantisPlugin</CODE
> class, which must be named in the
					form of <CODE
CLASS="CLASSNAME"
>%Basename%Plugin</CODE
>, which for our purpose
					becomes <CODE
CLASS="CLASSNAME"
>ExamplePlugin</CODE
>.
				</P
><P
>					Because of how <CODE
CLASS="CLASSNAME"
>MantisPlugin</CODE
> declares the
					<CODE
CLASS="FUNCTION"
>register()</CODE
>	method as <TT
CLASS="LITERAL"
>abstract</TT
>, 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.
				</P
><P
>					Once your plugin defines its class, implements the <CODE
CLASS="FUNCTION"
>register()</CODE
>
					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:
				</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/Example.php</TT
>

&lt;?php
class ExamplePlugin extends MantisPlugin {
    function register() {
        $this-&#62;name = 'Example';    # Proper name of plugin
        $this-&#62;description = '';    # Short description of the plugin
        $this-&#62;page = '';           # Default plugin page

        $this-&#62;version = '1.0';     # Plugin version string
        $this-&#62;requires = array(    # Plugin dependencies, array of basename =&#62; version pairs
            'MantisCore' =&#62; '1.2.0, &lt;= 1.2.0',  #   Should always depend on an appropriate version of MantisBT
            );

        $this-&#62;author = '';         # Author/team name
        $this-&#62;contact = '';        # Author/team e-mail address
        $this-&#62;url = '';            # Support webpage
    }
}
				</PRE
><P
>					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.
				</P
></DIV
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.PAGES"
>4.2.2. Pages and Files</A
></H3
><P
>				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.
			</P
><P
>				New pages for your plugin should be placed in your plugin's
				<TT
CLASS="FILENAME"
>pages/</TT
> 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 <CODE
CLASS="FUNCTION"
>plugin_page()</CODE
> should be used.
				Our Example plugin will create a page named <TT
CLASS="FILENAME"
>foo.php</TT
>, which
				can then be accessed via <TT
CLASS="LITERAL"
>plugin_page.php?page=Example/foo</TT
>, the
				same URI that <CODE
CLASS="FUNCTION"
>plugin_page()</CODE
> would have generated:
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/foo.php</TT
>

&lt;?php
echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '"&#62;page foo&lt;/a&gt;.&lt;/p&gt;';
			</PRE
><P
>				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
				<TT
CLASS="FILENAME"
>files/</TT
> directory, and can only contain a single period in
				the name.  The file's URI is generated with the <CODE
CLASS="FUNCTION"
>plugin_file()</CODE
>
				function.  For our Example plugin, we'll create a basic CSS stylesheet, and modify
				the previously shown page to include the stylesheet:
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/files/foo.css</TT
>

p.foo {
    color: red;
}
			</PRE
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/foo.php</TT
>

&lt;?php
echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '"&#62;page foo&lt;/a&gt;.&lt;/p&gt;';
echo '&lt;link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/&gt;',
     '&lt;p class="foo"&gt;This is red text.&lt;/p&gt;';
			</PRE
><P
>				Note that while <CODE
CLASS="FUNCTION"
>plugin_page()</CODE
> expects on the page's name,
				without the extension, <CODE
CLASS="FUNCTION"
>plugin_file()</CODE
> requires the entire
				filename so that it can distinguish between <TT
CLASS="FILENAME"
>foo.css</TT
> and
				a potential file <TT
CLASS="FILENAME"
>foo.png</TT
>.
			</P
><P
>				The plugin's filesystem structure at this point looks like this:
			</P
><PRE
CLASS="PROGRAMLISTING"
>Example/
	Example.php
	pages/
		foo.php
	files/
		foo.css
			</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.EVENTS"
>4.2.3. Events</A
></H3
><P
>				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.
			</P
><P
>				To declare a new event, or a set of events, that your plugin will trigger, override
				the <CODE
CLASS="FUNCTION"
>events()</CODE
> 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):
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/Example.php</TT
>

&lt;?php
class ExamplePlugin extends MantisPlugin {
    ...

    function events() {
        return array(
            'EVENT_EXAMPLE_FOO' =&#62; EVENT_TYPE_EXECUTE,
            'EVENT_EXAMPLE_BAR' =&#62; EVENT_TYPE_CHAIN,
        );
    }
}
			</PRE
><P
>				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.
			</P
><P
>				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 <CODE
CLASS="FUNCTION"
>foo()</CODE
> and
				<CODE
CLASS="FUNCTION"
>bar()</CODE
> method on our plugin class, and hook them to the events we
				declared earlier.
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/Example.php</TT
>

&lt;?php
class ExamplePlugin extends MantisPlugin {
    ...

    function hooks() {
        return array(
            'EVENT_EXAMPLE_FOO' =&#62; 'foo',
            'EVENT_EXAMPLE_BAR' =&#62; 'bar',
        );
    }

    function foo( $p_event ) {
        ...
    }

    function bar( $p_event, $p_chained_param ) {
        ...
        return $p_chained_param;
    }
}
			</PRE
><P
>				Note that both hooked methods need to accept the <CODE
CLASS="PARAMETER"
>$p_event</CODE
>
				parameter, as that contains the event name triggering the method (for cases where
				you may want a method hooked to multiple events).  The <CODE
CLASS="FUNCTION"
>bar()</CODE
>
				method also accepts and returns the chained parameter in order to match the
				expectations of the "bar" event.
			</P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/Example.php</TT
>

&lt;?php
class ExamplePlugin extends MantisPlugin {
    ...

    function foo( $p_event ) {
        echo 'In method foo(). ';
    }

    function bar( $p_event, $p_chained_param ) {
        return str_replace( 'foo', 'bar', $p_chained_param );
    }
}
			</PRE
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/foo.php</TT
>

&lt;?php
echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '"&#62;page foo&lt;/a&gt;.&lt;/p&gt;';
     '&lt;link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/&gt;',
     '&lt;p class="foo"&gt;';

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, '&lt;/p&gt;';
			</PRE
><P
>				When the first event "foo" is signaled, the Example plugin's
				<CODE
CLASS="FUNCTION"
>foo()</CODE
> 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 <CODE
CLASS="FUNCTION"
>bar()</CODE
> 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.
			</P
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.CONFIG"
>4.2.4. Configuration</A
></H3
><P
>				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.
			</P
><P
>				Declaring a new configuration option is achieved just like declaring events.  By
				overriding the <CODE
CLASS="FUNCTION"
>config()</CODE
> 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":
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/Example.php</TT
>

&lt;?php
class ExamplePlugin extends MantisPlugin {
    ...

    function config() {
        return array(
            'foo_or_bar' =&#62; 'foo',
        );
    }
}
			</PRE
><P
>				Retrieving the current value of a plugin's configuration option is achieved by
				using the plugin API's <CODE
CLASS="FUNCTION"
>plugin_config_get()</CODE
> function, and can
				be set to a modified value in the database using
				<CODE
CLASS="FUNCTION"
>plugin_config_set()</CODE
>.  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:
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/config_page.php</TT
>

&lt;form action="&lt;?php echo plugin_page( 'config_update' ) ?&gt;" method="post"&gt;
&lt;?php echo form_security_field( 'plugin_Example_config_update' ) ?&gt;

&lt;label&gt;Foo or Bar?&lt;br/&gt;&lt;input name="foo_or_bar" value="&lt;?php echo string_attribute( $t_foo_or_bar ) ?&gt;"/&gt;&lt;/label&gt;
&lt;br/&gt;
&lt;label&gt;&lt;input type="checkbox" name="reset"/&gt; Reset&lt;/label&gt;
&lt;br/&gt;
&lt;input type="submit"/&gt;

&lt;/form&gt;
			</PRE
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/config_update.php</TT
>

&lt;?php
form_security_validate( 'plugin_Example_config_update' );

$f_foo_or_bar = gpc_get_string( 'foo_or_bar' );
$f_reset = gpc_get_bool( 'reset', false );

if ( $f_reset ) {
    plugin_config_delete( 'foo_or_bar' );
} else {
    if ( $f_foo_or_bar == 'foo' || $f_foo_or_bar == 'bar' ) {
        plugin_config_set( 'foo_or_bar', $f_foo_or_bar );
    }
}

form_security_purge( 'plugin_Example_config_update' );
print_successful_redirect( plugin_page( 'foo', true ) );
			</PRE
><P
>				Note that the <CODE
CLASS="FUNCTION"
>form_security_*()</CODE
> functions are part of the
				form API, and prevent CSRF attacks against forms that make changes to the system.
			</P
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.LANGUAGE"
>4.2.5. Language and Localization</A
></H3
><P
>				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.
			</P
><P
>				All language files for plugins follow the same format used in the core of MantisBT,
				should be placed in the plugin's <TT
CLASS="FILENAME"
>lang/</TT
> 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.
			</P
><P
>				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:
			</P
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/lang/strings_english.txt</TT
>

&lt;?php

$s_plugin_Example_configuration = "Configuration";
$s_plugin_Example_foo_or_bar = "Foo or Bar?";
$s_plugin_Example_reset = "Reset Value";
			</PRE
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/config_page.php</TT
>
&lt;?php

html_page_top( plugin_lang_get( 'configuration' ) );
$t_foo_or_bar = plugin_config_get( 'foo_or_bar' );

?&gt;

&lt;br/&gt;

&lt;form action="&lt;?php echo plugin_page( 'config_update' ) ?&gt;" method="post"&gt;
&lt;?php echo form_security_field( 'plugin_Example_config_update' ) ?&gt;
&lt;table class="width60" align="center"&gt;

&lt;tr&gt;
    &lt;td class="form-title" rowspan="2"&gt;&lt;?php echo plugin_lang_get( 'configuration' ) ?&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr &lt;?php echo helper_alternate_class() ?&gt;&gt;
    &lt;td class="category"&gt;&lt;php echo plugin_lang_get( 'foo_or_bar' ) ?&gt;&lt;/td&gt;
    &lt;td&gt;&lt;input name="foo_or_bar" value="&lt;?php echo string_attribute( $t_foo_or_bar ) ?&gt;"/&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr &lt;?php echo helper_alternate_class() ?&gt;&gt;
    &lt;td class="category"&gt;&lt;php echo plugin_lang_get( 'reset' ) ?&gt;&lt;/td&gt;
    &lt;td&gt;&lt;input type="checkbox" name="reset"/&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td class="center" rowspan="2"&gt;&lt;input type="submit"/&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/form&gt;

&lt;?php

html_page_bottom();
			</PRE
><P
>				The two calls to <CODE
CLASS="FUNCTION"
>html_page_top()</CODE
> and
				<CODE
CLASS="FUNCTION"
>html_page_bottom()</CODE
> trigger the standard MantisBT header and
				footer portions, respectively, which also displays things such as the menus and
				triggers other layout-related events.  <CODE
CLASS="FUNCTION"
>helper_alternate_class()</CODE
>
				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.
			</P
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE"
>4.3. Example Plugin Source Listing</A
></H2
><P
>		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.
	</P
><PRE
CLASS="PROGRAMLISTING"
>Example/
    Example.php
    files/
        foo.css
    lang/
        strings_english.txt
    pages/
        config_page.php
        config_update.php
        foo.php
	</PRE
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE.EXAMPLE.PHP"
>4.3.1. Example/Example.php</A
></H3
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/Example.php</TT
>
&lt;?php
class ExamplePlugin extends MantisPlugin {
    function register() {
        $this-&#62;name = 'Example';    # Proper name of plugin
        $this-&#62;description = '';    # Short description of the plugin
        $this-&#62;page = '';           # Default plugin page

        $this-&#62;version = '1.0';     # Plugin version string
        $this-&#62;requires = array(    # Plugin dependencies, array of basename =&#62; version pairs
            'MantisCore' =&#62; '1.2.0, &lt;= 1.2.0',  #   Should always depend on an appropriate version of MantisBT
            );

        $this-&#62;author = '';         # Author/team name
        $this-&#62;contact = '';        # Author/team e-mail address
        $this-&#62;url = '';            # Support webpage
    }

    function events() {
        return array(
            'EVENT_EXAMPLE_FOO' =&#62; EVENT_TYPE_EXECUTE,
            'EVENT_EXAMPLE_BAR' =&#62; EVENT_TYPE_CHAIN,
        );
    }

    function hooks() {
        return array(
            'EVENT_EXAMPLE_FOO' =&#62; 'foo',
            'EVENT_EXAMPLE_BAR' =&#62; 'bar',
        );
    }

    function config() {
        return array(
            'foo_or_bar' =&#62; 'foo',
        );
    }

    function foo( $p_event ) {
        echo 'In method foo(). ';
    }

    function bar( $p_event, $p_chained_param ) {
        return str_replace( 'foo', 'bar', $p_chained_param );
    }

}
		</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE.FOO.CSS"
>4.3.2. Example/files/foo.css</A
></H3
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/files/foo.css</TT
>
p.foo {
    color: red;
}
		</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE.STRINGSENGLISH.TXT"
>4.3.3. Example/lang/strings_english.txt</A
></H3
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/lang/strings_english.txt</TT
>
&lt;?php

$s_plugin_Example_configuration = "Configuration";
$s_plugin_Example_foo_or_bar = "Foo or Bar?";
$s_plugin_Example_reset = "Reset Value";
		</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE.CONFIGPAGE.PHP"
>4.3.4. Example/page/config_page.php</A
></H3
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/config_page.php</TT
>
&lt;?php

html_page_top( plugin_lang_get( 'configuration' ) );
$t_foo_or_bar = plugin_config_get( 'foo_or_bar' );

?&gt;

&lt;br/&gt;

&lt;form action="&lt;?php echo plugin_page( 'config_update' ) ?&gt;" method="post"&gt;
&lt;?php echo form_security_field( 'plugin_Example_config_update' ) ?&gt;
&lt;table class="width60" align="center"&gt;

&lt;tr&gt;
    &lt;td class="form-title" rowspan="2"&gt;&lt;?php echo plugin_lang_get( 'configuration' ) ?&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr &lt;?php echo helper_alternate_class() ?&gt;&gt;
    &lt;td class="category"&gt;&lt;php echo plugin_lang_get( 'foo_or_bar' ) ?&gt;&lt;/td&gt;
    &lt;td&gt;&lt;input name="foo_or_bar" value="&lt;?php echo string_attribute( $t_foo_or_bar ) ?&gt;"/&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr &lt;?php echo helper_alternate_class() ?&gt;&gt;
    &lt;td class="category"&gt;&lt;php echo plugin_lang_get( 'reset' ) ?&gt;&lt;/td&gt;
    &lt;td&gt;&lt;input type="checkbox" name="reset"/&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;tr&gt;
    &lt;td class="center" rowspan="2"&gt;&lt;input type="submit"/&gt;&lt;/td&gt;
&lt;/tr&gt;

&lt;/table&gt;
&lt;/form&gt;

&lt;?php

html_page_bottom();
		</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE.CONFIGUPDATE.PHP"
>4.3.5. Example/pages/config_update.php</A
></H3
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/config_update.php</TT
>
&lt;?php
form_security_validate( 'plugin_Example_config_update' );

$f_foo_or_bar = gpc_get_string( 'foo_or_bar' );
$f_reset = gpc_get_bool( 'reset', false );

if ( $f_reset ) {
    plugin_config_delete( 'foo_or_bar' );
} else {
    if ( $f_foo_or_bar == 'foo' || $f_foo_or_bar == 'bar' ) {
        plugin_config_set( 'foo_or_bar', $f_foo_or_bar );
    }
}

form_security_purge( 'plugin_Example_config_update' );
print_successful_redirect( plugin_page( 'foo', true ) );
		</PRE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.PLUGINS.BUILDING.SOURCE.FOO.PHP"
>4.3.6. Example/page/foo.php</A
></H3
><PRE
CLASS="PROGRAMLISTING"
><TT
CLASS="FILENAME"
>Example/pages/foo.php</TT
>
&lt;?php
echo '&lt;p&gt;Here is a link to &lt;a href="', plugin_page( 'foo' ), '"&#62;page foo&lt;/a&gt;.&lt;/p&gt;';
     '&lt;link rel="stylesheet" type="text/css" href="', plugin_file( 'foo.css' ), '"/&gt;',
     '&lt;p class="foo"&gt;';

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, '&lt;/p&gt;';
		</PRE
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.PLUGINS.API"
>4.4. API Usage</A
></H2
><P
>			This is a general overview of the plugin API.  For more detailed analysis,
			you may reference the file <TT
CLASS="FILENAME"
>core/plugin_api.php</TT
> in the
			codebase.
		</P
></DIV
></DIV
><DIV
CLASS="CHAPTER"
><HR><H1
><A
NAME="DEV.EVENTREF"
></A
>Chapter 5. Event Reference</H1
><DIV
CLASS="SECT1"
><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.INTRO"
>5.1. Introduction</A
></H2
><P
>			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.
		</P
><P
>			Here we show an example event definition.  For each event, the event identifier will be listed
			along with the <A
HREF="#DEV.EVENTS.TYPES"
>event type</A
> 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).
		</P
><A
NAME="DEV.EVENTREF.EXAMPLE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_EXAMPLE (Default)</B
></P
><A
NAME="AEN346"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This is an example event description.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Type&gt;: Description of parameter one</P
></LI
><LI
><P
>&lt;Type&gt;: Description of parameter two</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Type&gt;: Description of return value</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.SYSTEM"
>5.2. System Events</A
></H2
><P
>			These events are initiated by the plugin system itself to allow certain functionality to
			simplify plugin development.
		</P
><A
NAME="DEV.EVENTREF.SYSTEM.PLUGININIT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_PLUGIN_INIT (Execute)</B
></P
><A
NAME="AEN363"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event is triggered by the MantisBT plugin system after all registered and enabled
					plugins have been initialized (their <CODE
CLASS="FUNCTION"
>init()</CODE
> functions have been called).
					This event should <SPAN
CLASS="emphasis"
><I
CLASS="EMPHASIS"
>always</I
></SPAN
> be the first event triggered for any
					page load.  No parameters are passed to hooked functions, and no return values are
					expected.
				</P
><P
>					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.
				</P
><P
>					Suggested uses for the event include:
					<P
></P
><UL
><LI
><P
>Checking for plugins that aren't require for normal usage.</P
></LI
><LI
><P
>Interacting with other plugins outside the context of pages or events.</P
></LI
></UL
>
				</P
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.SYSTEM.COREREADY"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_CORE_READY (Execute)</B
></P
><A
NAME="AEN376"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
>					This event is the first point in page execution where the entire system is considered
					loaded and ready.
				</P
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.SYSTEM.LOG"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LOG (Execute)</B
></P
><A
NAME="AEN381"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.OUTPUT"
>5.3. Output Modifier Events</A
></H2
><DIV
CLASS="SECT2"
><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.OUTPUT.DISPLAY"
>5.3.1. String Display</A
></H3
><P
>			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.
		</P
><A
NAME="DEV.EVENTREF.OUTPUT.DISPLAY.BUGID"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_DISPLAY_BUG_ID (Chained)</B
></P
><A
NAME="AEN390"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This is an event to format bug ID numbers before being displayed, using the
					<CODE
CLASS="FUNCTION"
>bug_format_id()</CODE
> API call.  The result should be plain-text,
					as the resulting string is used in various formats and locations.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>String: bug ID string to be displayed</P
></LI
><LI
><P
>Int: bug ID number</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: modified bug ID string</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.DISPLAY.EMAIL"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_DISPLAY_EMAIL (Chained)</B
></P
><A
NAME="AEN405"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>String: input string to be displayed</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: modified input string</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.DISPLAY.FORMATTED"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_DISPLAY_FORMATTED (Chained)</B
></P
><A
NAME="AEN417"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>String: input string to be displayed</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: modified input string</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.DISPLAY.RSS"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_DISPLAY_RSS (Chained)</B
></P
><A
NAME="AEN429"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This is an event to format content before being displayed in an RSS feed.  Text
					should be processed to perform any necessary character escaping to preserve
					hyperlinks and other appropriate markup.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>String: input string to be displayed</P
></LI
><LI
><P
>Boolean: multiline input string</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: modified input string</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.DISPLAY.TEXT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_DISPLAY_TEXT (Chained)</B
></P
><A
NAME="AEN443"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>String: input string to be displayed</P
></LI
><LI
><P
>Boolean: multiline input string</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: modified input string</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.OUTPUT.MENU"
>5.3.2. Menu Items</A
></H3
><P
>			These events allow new menu items to be inserted in order for new content to be added,
			such as new pages or integration with other applications.
		</P
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.ACCOUNT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_ACCOUNT (Default)</B
></P
><A
NAME="AEN460"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event gives plugins the opportunity to add new links to the user account
					menu available to users from the 'My Account' link on the main menu.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the user account menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.DOCS"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_DOCS (Default)</B
></P
><A
NAME="AEN468"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event gives plugins the opportunity to add new links to the documents
					menu available to users from the 'Docs' link on the main menu.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the documents menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.FILTER"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_FILTER (Default)</B
></P
><A
NAME="AEN476"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event gives plugins the opportunity to add new links to the issue list
					menu available to users from the 'View Issues' link on the main menu.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the issue list menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.ISSUE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_ISSUE (Default)</B
></P
><A
NAME="AEN484"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event gives plugins the opportunity to add new links to the issue
					menu available to users when viewing issues.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the documents menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.MAIN"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_MAIN (Default)</B
></P
><A
NAME="AEN492"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the main menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.MAINFRONT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_MAIN_FRONT (Default)</B
></P
><A
NAME="AEN500"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the main menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.MANAGE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_MANAGE (Default)</B
></P
><A
NAME="AEN508"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the management menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.MANAGECONFIG"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_MANAGE_CONFIG (Default)</B
></P
><A
NAME="AEN516"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the manage configuration menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.MENU.SUMMARY"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MENU_SUMMARY (Default)</B
></P
><A
NAME="AEN524"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event gives plugins the opportunity to add new links to the summary menu
					available to users from the 'Summary' link on the main menu.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>Array: List of HTML links for the summary menu.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT"
>5.3.3. Page Layout</A
></H3
><P
>			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.
		</P
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.RESOURCES"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_RESOURCES (Output)</B
></P
><A
NAME="AEN535"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code from inside the
					<TT
CLASS="LITERAL"
>&lt;head&gt;</TT
> tag, for use with CSS, Javascript, RSS,
					or any other similary resources.  Note that this event is signaled
					after all other CSS and Javascript resources are linked by MantisBT.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.BODYBEGIN"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_BODY_BEGIN (Output)</B
></P
><A
NAME="AEN544"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code immediatly after the
					<TT
CLASS="LITERAL"
>&lt;body&gt;</TT
> tag is opened, so that MantisBT may be
					integrated within another website's template, or other similar use.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.PAGEHEADER"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_PAGE_HEADER (Output)</B
></P
><A
NAME="AEN553"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code immediatly after the
					MantisBT header content, such as the logo image.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.CONTENTBEGIN"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_CONTENT_BEGIN (Output)</B
></P
><A
NAME="AEN561"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code after the top main
					menu, but before any page-specific content begins.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.CONTENTEND"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_CONTENT_END (Output)</B
></P
><A
NAME="AEN569"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code after any page-
					specific content has completed, but before the bottom menu bar
					(or footer).
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.PAGEFOOTER"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_PAGE_FOOTER (Output)</B
></P
><A
NAME="AEN577"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code after the MantisBT
					version, copyright, and webmaster information, but before the
					query information.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.OUTPUT.LAYOUT.BODYEND"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_LAYOUT_BODY_END (Output)</B
></P
><A
NAME="AEN585"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to output HTML code immediatly before
					the <TT
CLASS="LITERAL"
>&lt;/body&gt;</TT
> end tag, to so that MantisBT may be
					integrated within another website's template, or other similar use.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>String: HTML code to output.</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.FILTER"
>5.4. Bug Filter Events</A
></H2
><DIV
CLASS="SECT2"
><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.FILTER.CUSTOM"
>5.4.1. Custom Filters and Columns</A
></H3
><A
NAME="DEV.EVENTREF.FILTER.CUSTOM.FIELDS"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_FILTER_FIELDS (Default)</B
></P
><A
NAME="AEN598"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to register custom filter objects (based
					on the <CODE
CLASS="CLASSNAME"
>MantisFilter</CODE
> class) that will allow the user
					to search for issues based on custom criteria or datasets.  The plugin
					must ensure that the filter class has been defined before returning
					the class name for this event.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Array&gt;: Array of class names for custom filters</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.FILTER.CUSTOM.COLUMNS"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_FILTER_COLUMNS (Default)</B
></P
><A
NAME="AEN607"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to register custom column objects (based
					on the <CODE
CLASS="CLASSNAME"
>MantisColumn</CODE
> class) that will allow the
					user to view data for issues based on custom datasets.  The plugin
					must ensure that the column class has been defined before returning
					the class name for this event.
				</P
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Array&gt;: Array of class names for custom columns</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.BUG"
>5.5. Bug and Bugnote Events</A
></H2
><DIV
CLASS="SECT2"
><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.BUG.VIEW"
>5.5.1. Bug View</A
></H3
><A
NAME="DEV.EVENTREF.BUG.VIEW.DETAILS"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_VIEW_BUG_DETAILS (Execute)</B
></P
><A
NAME="AEN620"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding <PRE
CLASS="PROGRAMLISTING"
>&lt;table&gt;</PRE
> elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.VIEW.EXTRA"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_VIEW_BUG_EXTRA (Execute)</B
></P
><A
NAME="AEN630"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to either process information or display some
					data in the bug view page.  It is triggered after the bug notes have been
					displayed, but before the history log is shown.
				</P
><P
>					Any output here should be contained within its own
					<PRE
CLASS="PROGRAMLISTING"
>&lt;table&gt;</PRE
> element.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.BUG.ACTION"
>5.5.2. Bug Actions</A
></H3
><A
NAME="DEV.EVENTREF.BUG.ACTION.REPORTBUGFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_REPORT_BUG_FORM (Execute)</B
></P
><A
NAME="AEN642"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements on
					the Report Issue page.  It is triggered immediately before the summary
					text field.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Project ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.REPORTBUGFORMTOP"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_REPORT_BUG_FORM_TOP (Execute)</B
></P
><A
NAME="AEN651"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements at
					the top of the Report Issue page.  It is triggered before any of the
					visible form elements have been created.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Project ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.REPORTBUGDATA"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_REPORT_BUG_DATA (Chain)</B
></P
><A
NAME="AEN660"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Complex&gt;: Bug data structure (see <TT
CLASS="FILENAME"
>core/bug_api.php</TT
>)</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Complex&gt;: Bug data structure</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.REPORTBUG"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_REPORT_BUG (Execute)</B
></P
><A
NAME="AEN673"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Complex&gt;: Bug data structure (see <TT
CLASS="FILENAME"
>core/bug_api.php</TT
>)</P
></LI
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.UPDATEBUGFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_UPDATE_BUG_FORM (Execute)</B
></P
><A
NAME="AEN684"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements on
					the Update Issue page.  It is triggered immediately before the summary
					text field.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.UPDATEBUGFORMTOP"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_UPDATE_BUG_FORM_TOP (Execute)</B
></P
><A
NAME="AEN692"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements on
					the Update Issue page.  It is triggered immediately before before any
					of the visible form elements have been created.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.UPDATEBUG"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_UPDATE_BUG (Chain)</B
></P
><A
NAME="AEN700"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to perform both pre- and post-processing of
					the updated bug data structure after being modified by the user, but
					before being saved to the database.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Complex&gt;: Bug data structure (see <TT
CLASS="FILENAME"
>core/bug_api.php</TT
>)</P
></LI
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Complex&gt;: Bug data structure</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.BUGACTION"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUG_ACTION (Execute)</B
></P
><A
NAME="AEN715"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to perform post-processing of group actions
					performed from the View Issues page.  The event will get called for
					each bug ID that was part of the group action event.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;String&gt;: Action title (see <TT
CLASS="FILENAME"
>bug_actiongroup.php</TT
>)</P
></LI
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.BUGDELETE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUG_DELETED (Execute)</B
></P
><A
NAME="AEN726"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to perform pre-processing of bug deletion
					actions.  The actual deletion will occur after execution of the event,
					for compatibility reasons.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.BUG.NOTEVIEW"
>5.5.3. Bugnote View</A
></H3
><A
NAME="DEV.EVENTREF.BUG.NOTEVIEW.START"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_VIEW_BUGNOTES_START (Execute)</B
></P
><A
NAME="AEN736"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to either process information or display some
					data in the bug notes section, before any bug notes are displayed.  It is
					triggered after the bug notes section title.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Complex&gt;: A list of all bugnotes to be displayed to the user</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.NOTEVIEW.NOTE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_VIEW_BUGNOTE (Execute)</B
></P
><A
NAME="AEN747"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to either process information or display some
					data in the bug notes section, interleaved with the individual bug notes.
					It gets triggered after every bug note is displayed.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Integer&gt;: Bugnote ID</P
></LI
><LI
><P
>&lt;Boolean&gt;: Private bugnote (false if public)</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.NOTEVIEW.END"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_VIEW_BUGNOTES_END (Execute)</B
></P
><A
NAME="AEN760"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to either process information or display some
					data in the bug notes section, after all bugnotes have been displayed.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.BUG.NOTEACTION"
>5.5.4. Bugnote Actions</A
></H3
><A
NAME="DEV.EVENTREF.BUG.NOTEACTION.ADDFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUGNOTE_ADD_FORM (Execute)</B
></P
><A
NAME="AEN771"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements in
					the bugnote adding form.  It is triggered immediately after the bugnote
					text field.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.NOTEACTION.ADD"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUGNOTE_ADD (Execute)</B
></P
><A
NAME="AEN780"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of bugnotes added to an
					issue.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Integer&gt;: Bugnote ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.NOTEACTION.EDITFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUGNOTE_EDIT_FORM (Execute)</B
></P
><A
NAME="AEN790"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements in
					the bugnote editing form.  It is triggered immediately after the bugnote
					text field.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Integer&gt;: Bugnote ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.NOTEACTION.EDIT"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUGNOTE_EDIT (Execute)</B
></P
><A
NAME="AEN801"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of bugnote edits.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Integer&gt;: Bugnote ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.NOTEACTION.DELETED"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_BUGNOTE_DELETED (Execute)</B
></P
><A
NAME="AEN811"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of bugnote deletions.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Integer&gt;: Bugnote ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.TAGATTACHED"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TAG_ATTACHED (Execute)</B
></P
><A
NAME="AEN821"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of attached tags.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Array of Integers&gt;: Tag IDs</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.BUG.ACTION.TAGDETACHED"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_TAG_DETACHED (Execute)</B
></P
><A
NAME="AEN831"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of detached tags.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;Array of Integers&gt;: Tag IDs</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.NOTIFY"
>5.6. Notification Events</A
></H2
><DIV
CLASS="SECT2"
><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.NOTIFY.USER"
>5.6.1. Recipient Selection</A
></H3
><A
NAME="DEV.EVENTREF.NOTIFY.USER.INCLUDE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_NOTIFY_USER_INCLUDE (Default)</B
></P
><A
NAME="AEN845"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					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.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;String&gt;: Notification type</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Array&gt;: User IDs to include as recipients</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.NOTIFY.USER.EXCLUDE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_NOTIFY_USER_EXCLUDE (Default)</B
></P
><A
NAME="AEN859"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows a plugin to selectively exclude indivdual users from
					the recipient list for a notification.  The event is signalled for every
					user in the final reicipient list, including recipients added by the
					event NOTIFY_USER_INCLUDE as described above.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Bug ID</P
></LI
><LI
><P
>&lt;String&gt;: Notification type</P
></LI
><LI
><P
>&lt;Integer&gt;: User ID</P
></LI
></UL
><P
></P
><P
><B
>Return Value</B
></P
><UL
><LI
><P
>&lt;Boolean&gt;: True to exclude the user, false otherwise</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.ACCOUNT"
>5.7. User Account Events</A
></H2
><DIV
CLASS="SECT2"
><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.ACCOUNT.PREFS"
>5.7.1. Account Preferences</A
></H3
><A
NAME="DEV.EVENTREF.ACCOUNT.PREFS.UPDATEFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_ACCOUNT_PREFS_UPDATE_FORM (Execute)</B
></P
><A
NAME="AEN879"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements on
					the Account Preferences page.  It is triggered immediately after the last
					core preference element.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: User ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.ACCOUNT.PREFS.UPDATE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_ACCOUNT_PREFS_UPDATE (Execute)</B
></P
><A
NAME="AEN888"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do pre-processing of form elements from the
					Account Preferences page.  It is triggered immediately before the user
					preferences are saved to the database.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: User ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
></DIV
><DIV
CLASS="SECT1"
><HR><H2
CLASS="SECT1"
><A
NAME="DEV.EVENTREF.MANAGE"
>5.8. Management Events</A
></H2
><A
NAME="DEV.EVENTREF.MANAGE.OVERVIEWINFO"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_OVERVIEW_INFO (Output)</B
></P
><A
NAME="AEN898"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>				This event allows plugins to display special information on the
				Management Overview page.
			</P
><P
>				Any output here should be defining appropriate rows and columns for the
				surrounding &lt;table&gt; elements.
			</P
></BLOCKQUOTE
></BLOCKQUOTE
><DIV
CLASS="SECT2"
><HR><H3
CLASS="SECT2"
><A
NAME="DEV.EVENTREF.MANAGE.PROJECT"
>5.8.1. Projects and Versions</A
></H3
><A
NAME="DEV.EVENTREF.MANAGE.PROJECT.VIEW"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_PROJECT_PAGE (Execute)</B
></P
><A
NAME="AEN905"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display information on
					the View Project page.  It is triggered immediately before the project
					access blocks.
				</P
><P
>					Any output here should be contained within its own &lt;table&gt; element.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Project ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.PROJECT.CREATEFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_PROJECT_CREATE_FORM (Execute)</B
></P
><A
NAME="AEN914"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements on
					the Create Project page.  It is triggered immediately before the submit
					button.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.PROJECT.CREATE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_PROJECT_CREATE (Execute)</B
></P
><A
NAME="AEN919"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of newly-created projects
					and form elements from the Create Project page.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Project ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.PROJECT.UPDATEFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_PROJECT_UPDATE_FORM (Execute)</B
></P
><A
NAME="AEN927"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements in
					the Edit Project form on the View Project page.  It is triggered
					immediately before the submit button.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Project ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.PROJECT.UPDATE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_PROJECT_UPDATE (Execute)</B
></P
><A
NAME="AEN936"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of modified projects
					and form elements from the Edit Project form.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Project ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.VERSION.CREATE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_VERSION_CREATE (Execute)</B
></P
><A
NAME="AEN944"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of newly-created
					project versions from the View Project page, or versions copied from
					other projects.  This event is triggered for each version created.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Version ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.VERSION.UPDATEFORM"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_VERSION_UPDATE_FORM (Execute)</B
></P
><A
NAME="AEN952"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do processing or display form elements on
					the Update Version page.  It is triggered immediately before the submit
					button.
				</P
><P
>					Any output here should be defining appropriate rows and columns for the
					surrounding &lt;table&gt; elements.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Version ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
><A
NAME="DEV.EVENTREF.MANAGE.VERSION.UPDATE"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
><B
>EVENT_MANAGE_VERSION_UPDATE (Execute)</B
></P
><A
NAME="AEN961"
></A
><BLOCKQUOTE
CLASS="BLOCKQUOTE"
><P
>					This event allows plugins to do post-processing of modified versions
					and form elements from the Edit Version page.
				</P
><P
></P
><P
><B
>Parameters</B
></P
><UL
><LI
><P
>&lt;Integer&gt;: Version ID</P
></LI
></UL
></BLOCKQUOTE
></BLOCKQUOTE
></DIV
></DIV
></DIV
><DIV
CLASS="CHAPTER"
><HR><H1
><A
NAME="DEV.APPENDIX"
></A
>Chapter 6. Appendix</H1
><DIV
CLASS="SECT1"
><H2
CLASS="SECT1"
><A
NAME="DEV.APPENDIX.GIT"
>6.1. Git References</A
></H2
><P
></P
><UL
><LI
><P
>				<A
HREF="http://www.kernel.org/pub/software/scm/git/docs/"
TARGET="_top"
>Git Official Documentation</A
>
			</P
></LI
><LI
><P
>				<A
HREF="http://www.kernel.org/pub/software/scm/git/docs/gittutorial.html"
TARGET="_top"
>Git Tutorial</A
>
			</P
></LI
><LI
><P
>				<A
HREF="http://www.kernel.org/pub/software/scm/git/docs/everyday.html"
TARGET="_top"
>Everyday Git With 20 Commands</A
>
			</P
></LI
><LI
><P
>				<A
HREF="http://git.or.cz/course/svn.html"
TARGET="_top"
>Git Crash Course for SVN Users</A
>
			</P
></LI
><LI
><P
>				<A
HREF="http://www.newartisans.com/blog_files/git.from.bottom.up.php"
TARGET="_top"
>Git From the Bottom Up</A
>
			</P
></LI
><LI
><P
>				<A
HREF="http://www.gitcasts.com/"
TARGET="_top"
>GitCasts</A
>
			</P
></LI
></UL
></DIV
></DIV
></DIV
></BODY
></HTML
>
