=head1 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 L 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. =head1 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: =over =item Module::Install::RTx Sets up your extension to be installed using Module::Install. =item Dist::Zilla::MintingProfile::RTx Provides some tools for managing your distribution. Handy even if you're not putting your code on CPAN. =back If this is your first time using L, you can set up your CPAN details by running: dzil setup You can read about L and the C command at L. 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 F 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. =head1 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. =head2 Module Code In your new extension directory you'll already have a C 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 C as needed. =head2 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 L for more information. Your Mason templates should go in an C 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 C 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. =head2 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 C directory at the top level under your extension, and under that a C directory and a C directory. Before RT 4.2, you should create C and C directories in C. To add files to RT's include paths, you can use the L and L methods available in the L 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'); =head2 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 F file. If you need this, the file should go in the C directory. This will allow users to easily run the F file when installing with: make initdb =head2 Module::Install Files As mentioned above, the RT extension tools are set up to use L to manage the distribution. When you run perl Makefile.PL for the first time, L will create an C directory for all of the files it needs. Since you are the author, a C<.author> directory (note the . in the directory name) is created for you in the C directory. When L detects this directory, it does things only the author needs, like pulling in modules to put in the C directory. Once you have this set up, L should mostly do the right thing. You can find details in the module documentation. =head2 Tests =head3 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 C directory rather than the typical C directory. =head3 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 C 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 L which gives you access to the test RT and a test database for running tests. For this, you'll create a F file in your C tree. The easiest way to set up the test module to pull in F is to look at an example extension. L, for example, has a testing configuration you can borrow from. You'll notice that the file included in the extension is F. This is because there are paths that are set based on your RT location, so the actual F file is written when you run F with appropriate paths substituted when F is run. L provides an interface to make this easy with a C feature. The substitution code is in the F 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 L 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 C method using the Modulino approach. You can find an example of this approach in L in the F directory. =head2 Patches If you need to provide patches to RT for any reason, you can put them in a C directory. See L for more information. =head1 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. =head2 Directory Structure RT looks in the F directory under the RT base directory for extensions registered with the C<@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 F as part of the installation process. You need to make sure the path under C is correct since that is installed as-is. The C 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 C, you should create a F directory under F. 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 C 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 F that looks like this: $m->callback( %ARGS, CallbackName => 'AfterWorked', Ticket => $TicketObj ); You look at the F 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 F 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 F, 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 C parameter, name your file F and it will get invoked since that is the default callback name when one isn't provided. =head2 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 C, 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 C call to modify search results on a L object, or setting a flag like C<$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. =head1 Adding and Modifying Menus You can modify all of RT's menus using callbacks as described in L. 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 F file itself for examples of adding menu items. The menu object is a L 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', ) } =head1 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 L document. We generally accept patches that add new callbacks. Create a C 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 C<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. =head1 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 C and you created the F directory as described above, a F file will be created for you. You can now type: make manifest and a F 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 F directory which contains L 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 F utility provided by L or your favorite method of uploading to CPAN. =cut