Using Callbacks in RT

We've used Best Practical's Request Tracker (RT) for several years now for managing both customer support queries and our own internal processes, and over that time I've always customised it by simply hacking the existing Mason pages and putting them in rt3/local/ to override the original versions. However this is always a huge pain when it comes to upgrades as I have to backport all my changes to the new version. So for my latest tweak I thought that I would have a go at using the Callback mechanism instead.

Callbacks are breakpoints in the RT code that allow you to add extensions or override some existing functionality. They are not quite as flexible as editing the original pages, but because the changes are kept separate they should be much easier to maintain. Unfortunately they are not as well documented as other parts of RT which is probably why I'd not really looked at them properly before.

A callback is created by simply placing a Mason page in the rt3/local/html/Callbacks/*/ path that corresponds to the page and callback point that you are trying to use. The * is simply any string that you might want to use to group your callbacks together (I've used "RunPCRun" in this example).

The feature I wanted to add was to replace the textbox for a custom field called "Account" with a dropdown that was populated from an SQL query to an external data source. The callback in question is located in html/Elements/EditCustomField, and is called "EditComponentName" (unlike most callbacks that are simply called "Default"). Here's the relevant part of the page:

my $EditComponent = "EditCustomField$Type";
_CallbackName => 'EditComponentName',
Name => \$EditComponent,
CustomField => $CustomField,
Object => $Object );
$EditComponent = "EditCustomField$Type" unless $m->comp_exists($EditComponent);

So I created file called "EditComponentName" in "rt3/local/html/Callbacks/RunPCRun/Elements/EditCustomField/" with the following contents:

if ($CustomField->Name eq 'Account') {
$$Name = "EditCustomFieldAccount";
$CustomField => undef
$Name => undef

Now whenever a custom field widget is displayed, the callback code checks to see if the field is called "Account" and if it is then it sets the EditComponent variable (using the Name reference) to "EditCustomFieldAccount". The EditComponent variable is used by RT to determine the name of the file containing the widget code. Normally this is set to "EditCustomField$Type" where type is the determined by the custom field type ("FreeForm" in this particular case), but by overriding it with the callback I can now create a file called "rt3/local/html/Elements/EditCustomFieldAccount" which contains my custom widget code:

<select name="<%$NamePrefix.$CustomField->Id.'-Value'%>">
<option value="NONE">NONE</option>
% my $currentvalue;
% for my $customer (@{$session{$cache_key}}) {
<option value="<% $customer->{Code} %>"
<% (($customer->{Code} eq $Default) ? 'selected="selected"' : '') |n %>>
<%$customer->{Name}%> (<%$customer->{Code}%>)
% }
my $cache_key = "SelectAccount---" . $session{'CurrentUser'}->Id;
if (not defined $session{$cache_key}) {
# Get list of customers
my $Customers = new RT::Customers($session{'CurrentUser'});
while (my $customer = $Customers->Next) {
push @{$session{$cache_key}}, {
Id => $customer->Id,
Name => $customer->Name,
Code => $customer->Code,
$CustomField => undef
$NamePrefix => undef
$Default => 0