RT 4.2.12 Documentation
Writing extensions
- Introduction
- Getting Started
- Extension Directories
- Callbacks
- Adding and Modifying Menus
- Changes to RT
- Preparing for CPAN
Introduction
RT has a lot of core features, but sometimes you have a problem to solve that's beyond the scope of just configuration. The standard way to add features to RT is with an extension. You can see the large number of freely available extensions on CPAN under the RT::Extension namespace to get an idea what's already out there. We also list some of the more useful extensions on the Best Practical website at http://www.bestpractical.com/rt/extensions.html
After looking through those, you still may not find what you need, so you'll want to write your own extension. Through the years there have been different ways to safely and effectively add things onto RT. This document describes the current best practice which should allow you to add what you need and still be able to safely upgrade RT in the future.
Getting Started
There are a few modules that will set up your initial sandbox for you to get you started. Install these modules from CPAN:
- Module::Install::RTx
-
Sets up your extension to be installed using Module::Install.
- Dist::Zilla::MintingProfile::RTx
-
Provides some tools for managing your distribution. Handy even if you're not putting your code on CPAN.
If this is your first time using Dist::Zilla, you can set up your CPAN details by running:
dzil setup
You can read about Dist::Zilla and the dzil
command at http://dzil.org.
Change to the directory that will be the parent directory for your new extension and run the following, replacing Demo with a descriptive name for your new extension:
dzil new -P RTx RT-Extension-Demo
You'll see something like:
[DZ] making target dir /some-dir/RT-Extension-Demo
[DZ] writing files to /some-dir/RT-Extension-Demo
[DZ] dist minted in ./RT-Extension-Demo
If you're stuck on a name, take a look at some of the existing RT extensions. You can also ask around IRC (#rt on irc.perl.org) to see what people think makes sense for what the extension will do.
You'll now have a directory with the basic files for your extension. Included is a gitignore file, which is handy if you use git for your version control like we do. If you don't use git, feel free to delete it, but we hope you're using some sort of version control for your work.
Extension Directories
There are several places to put code to provide your new features and if you follow the guidelines below, you'll make sure things get installed in the right places when you're ready to use it. These standards apply to RT 4.0 and 4.2 and any differences between the two are noted below.
Module Code
In your new extension directory you'll already have a lib/RT/Extension/Demo.pm
file, which is just a standard perl module. As you start writing code, you can use all of the standard RT libraries because your extension will be running in the context of RT and those are already pulled in. You can also create more modules under lib
as needed.
Mason Code
RT provides callbacks throughout its Mason templates to give you hooks to add features. The easiest way to modify RT is to add Mason template files that will use these callbacks. See "Callbacks" for more information. Your Mason templates should go in an html
directory with the appropriate directory structure to make sure the callbacks are executed.
If you are creating completely new pages for RT, you can put these under the html
directory also. You can create subdirectories as needed to add the page to existing RT paths (like Tools) or to create new directories for your extension.
CSS and Javascript
Where these files live differs between RT 4.2 and above, and RT 4.0 and below; if you need your extension to be compatible with both, you may need to provide both configurations. On RT 4.2 and above, create a static
directory at the top level under your extension, and under that a css
directory and a js
directory. Before RT 4.2, you should create css
and js
directories in html/NoAuth/
.
To add files to RT's include paths, you can use the "AddStyleSheets" in RT and "AddJavascript" in RT methods available in the RT module. You can put the lines near the top of your module code (in your "Demo.pm" file). If you set up the paths correctly, you should only need to set the file names like this:
RT->AddStyleSheets('myextension.css');
RT->AddJavaScript('myextension.js');
Creating Objects in RT
If you need to have users create a group, scrip, template, or some other object in their RT instance, you can automate this using an initialdata file. If you need this, the file should go in the etc
directory. This will allow users to easily run the initialdata file when installing with:
make initdb
Module::Install Files
As mentioned above, the RT extension tools are set up to use Module::Install to manage the distribution. When you run
perl Makefile.PL
for the first time, Module::Install will create an inc
directory for all of the files it needs. Since you are the author, a .author
directory (note the . in the directory name) is created for you in the inc
directory. When Module::Install detects this directory, it does things only the author needs, like pulling in modules to put in the inc
directory. Once you have this set up, Module::Install should mostly do the right thing. You can find details in the module documentation.
Tests
Test Directory
You can create tests for your new extension just as with other perl code you write. However, unlike typical CPAN modules where users run the tests as a step in the installation process, RT users installing extensions don't usually run tests. This is because running the tests requires your RT to be set up in development mode which involves installing some additional modules and having a test database. To prevent users from accidentally running the tests, which will fail without this testing setup, we put them in a xt
directory rather than the typical t
directory.
Writing Extension Tests
If you want to write and run tests yourself, you'll need a development RT instance set up. Since you are building an extension, you probably already have one. To start with testing, set the RTHOME
environment variable to the base directory of your RT instance so your extension tests run against the right instance. This is especially useful if you have your test RT installed in a non-standard location.
Next, you need to subclass from RT::Test which gives you access to the test RT and a test database for running tests. For this, you'll create a Test.pm file in your lib
tree. The easiest way to set up the test module to pull in RT::Test is to look at an example extension. RT::Extension::RepeatTicket, for example, has a testing configuration you can borrow from.
You'll notice that the file included in the extension is lib/RT/Extension/RepeatTicket/Test.pm.in. This is because there are paths that are set based on your RT location, so the actual Test.pm file is written when you run Makefile.PL with appropriate paths substituted when Makefile.PL is run. Module::Install provides an interface to make this easy with a substitute
feature. The substitution code is in the Makefile.PL file and you can borrow that as well.
Once you have that set up, add this to the top of your test files:
use RT::Extension::Demo::Test tests => undef;
and you'll be able to run tests in the context of a fully functioning RT instance. The RT::Test documentation describes some of the helper methods available and you can look at other extensions and the RT source code for examples of how to do things like create tickets, queues, and users, how to set rights, and how to modify tickets to simulate various RT tasks.
If you have a command-line component in your extension, the easiest way to test it is to set up a run
method using the Modulino approach. You can find an example of this approach in RT::Extension::RepeatTicket in the bin directory.
Patches
If you need to provide patches to RT for any reason, you can put them in a patches
directory. See "Changes to RT" for more information.
Callbacks
The RT codebase, mostly the Mason templates, contains hooks called callbacks that make it easy to add functionality without changing the RT code itself. RT invokes callbacks by looking in the source directories for files that might have extra code.
Directory Structure
RT looks in the local/plugins directory under the RT base directory for extensions registered with the @Plugins
configuration. RT then uses the following structure when looking for callbacks:
local/plugins/[ext name]/html/Callbacks/[custom name]/[rt mason path]/[callback name]
The extension installation process will handle some of this for you by putting your html directory under local/plugins/[ext name] as part of the installation process. You need to make sure the path under html
is correct since that is installed as-is.
The Callbacks
directory is required. The next directory can be named anything and is provided to allow RT owners to keep local files organized in a way that makes sense to them. In the case of an extension, you should name the directory the same as your extension. So if your extension is RT::Extension::Demo
, you should create a RT-Extension-Demo directory under Callbacks.
The rest of the path is determined by the RT Mason code and the callback you want to use. You can find callbacks by looking for calls to the callback
method in the RT Mason code. You can use something like this in your base RT directory:
# find share/html/ | xargs grep '\->callback'
As an example, assume you wanted to modify the ticket update page to put something after the Time Worked field. You run the above and see there is a callback in share/html/Ticket/Update.html that looks like this:
$m->callback( %ARGS, CallbackName => 'AfterWorked', Ticket => $TicketObj );
You look at the Update.html file and see that the callback is located right after the Time Worked field. To add some code that RT will run at that point, you would create the directory:
html/Callbacks/RT-Extension-Demo/Ticket/Update.html/
Note that Update.html is a file in the RT source, but it becomes a directory in your extension code. You then create a file with the name of the callback, in this case AfterWorked, and that's where you put your code. So the full path and file would be:
html/Callbacks/RT-Extension-Demo/Ticket/Update.html/AfterWorked
If you see a callback that doesn't have a CallbackName
parameter, name your file Default and it will get invoked since that is the default callback name when one isn't provided.
Callback Parameters
When you look at callbacks using the method above, the other important thing to consider is the parameter list. In addition to the CallbackName
, the other parameters listed in the callback will be passed to you to use as you develop your extension.
Getting these parameters is important because you'll likely need them in your code, getting data from the current ticket object, for example. These values are also often passed by reference, which allows you to modify them, potentially changing the behavior of the RT template when it continues executing after evaluating your code.
Some examples are adding a Limit
call to modify search results on a DBIx::SearchBuilder object, or setting a flag like $skip_update
for a callback like this:
$m->callback( CallbackName => 'BeforeUpdate', ARGSRef => \%ARGS, skip_update => \$skip_update,
checks_failure => $checks_failure, results => \@results, TicketObj => $TicketObj );
There are many different callbacks in RT and these are just a few examples to give you idea what you can do in your callback code. You can also look at other extensions for examples of how people use callbacks to modify and extend RT.
Adding and Modifying Menus
You can modify all of RT's menus using callbacks as described in "Callbacks". The file in RT that controls menus is:
share/html/Elements/Tabs
and you'll find a Privileged and SelfService callback which gives you access to those two sets of menus. In those callbacks, you can add to or change the main menu, the page menu, or the page widgets.
You can look at the Tabs file itself for examples of adding menu items. The menu object is a RT::Interface::Web::Menu and you can find details on the available parameters in the documentation.
Here are some simple examples of what you might do in a callback:
<%init>
# Add a brand new root menu item
my $bps = Menu()->child(
'bps', # any unique identifier
title => 'Corporate',
path => 'http://bestpractical.com'
);
#Add a submenu item to this root menu item
$bps->child(
'wiki',
title => 'Wiki',
path => 'http://wiki.bestpractical.com',
);
#Retrieve the 'actions' page menu item
if (my $actions = PageMenu->child('actions')) {
$actions->child(
'newitem',
title => loc('New Action'), path => '/new/thing/here',
)
}
</%init>
Changes to RT
When writing an extension, the goal is to provide all of the new functionality in your extension code using standard interfaces into RT. However, sometimes when you're working on an extension, you'll find you really need a change in RT itself to make your extension work. Often this is something like adding a new callback or a method to a core module that would be helpful for everyone.
Since any change to RT will only be included in the next version and forward, you'll need to provide something for users on current or older versions of RT. An easy way to do this is to provide a patch in your extension distribution. In general, you should only provide patches if you know they will eventually be merged into RT. Otherwise, you may have to provide versions of your patches for each release of RT. You can read more about getting changes accepted into RT in the hacking document. We generally accept patches that add new callbacks.
Create a patches
directory in your extension distribution to hold your patch files. Name the patch files with the latest version of RT that needs the patch. For example, if the patch is needed for RT 4.0.7, name your patch 4.0.7-some-patch.diff
. That tells users that if they are using RT 4.0.7 or earlier, they need to apply the patch. If your extension can be used for RT 3.8, you'll likely need to provide different patches using the same naming convention.
Also remember to update your install documentation to remind users to apply the patch.
Preparing for CPAN
When you have your extension ready and want to release it to the world, you can do so with a few simple steps.
Assuming you have run perl Makefile.PL
and you created the inc/.author directory as described above, a README file will be created for you. You can now type:
make manifest
and a MANIFEST file will be created. It should contain all of the needed to install and run your extension. If you followed the steps above, you'll have also have a inc directory which contains Module::Install code. Note that this code should also be included with your extension when you release it as it's part of the install process.
Next, check to see if everything is ready with:
make distcheck
If anything is missing, it will be reported and you can go fix it. When the check is clean, run:
make dist
and a new distribution will be created in the form of a tarred and gzipped file.
Now you can upload to cpan with the cpan-upload utility provided by CPAN::Uploader or your favorite method of uploading to CPAN.
← Back to index