summaryrefslogtreecommitdiff
path: root/rt/docs/writing_extensions.pod
blob: 10d146633525a1b223d37a552b320dc1cc7337b7 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
=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<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.

=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<Dist::Zilla>, you can set up your
CPAN details by running:

    dzil setup

You can read about L<Dist::Zilla> and the C<dzil> command at L<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 F<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.

=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<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 C<lib>
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</Callbacks> for more information.
Your Mason templates should go in an C<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
C<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.

=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<static> directory at the top level under your extension, and under
that a C<css> directory and a C<js> directory. Before RT 4.2, you should
create C<css> and C<js> directories in C<html/NoAuth/>.

To add files to RT's include paths, you can use the L<RT/AddStyleSheets> and
L<RT/AddJavascript> methods available in the L<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');

=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<initialdata>
file. If you need this, the file should go in the C<etc> directory. This will
allow users to easily run the F<initialdata> file when installing with:

    make initdb

=head2 Module::Install Files

As mentioned above, the RT extension tools are set up to use L<Module::Install>
to manage the distribution. When you run

    perl Makefile.PL

for the first time, L<Module::Install> will create an C<inc> 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<inc>
directory. When L<Module::Install> detects this directory, it does things only
the author needs, like pulling in modules to put in the C<inc> directory.
Once you have this set up, L<Module::Install> 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<xt> directory rather than the typical C<t> 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<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 L<RT::Test>
which gives you access to the test RT and a test database for running
tests. For this, you'll create a F<Test.pm> file in your C<lib> tree.
The easiest way to set up the test module to pull in F<RT::Test> is to look at
an example extension. L<RT::Extension::RepeatTicket>, for example, has a
testing configuration you can borrow from.

You'll notice that the file included in the extension is
F<lib/RT/Extension/RepeatTicket/Test.pm.in>. This is because there are paths
that are set based on your RT location, so the actual F<Test.pm> file is
written when you run F<Makefile.PL> with appropriate paths substituted
when F<Makefile.PL> is run. L<Module::Install> provides an interface to make
this easy with a C<substitute> feature. The substitution code is in the
F<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 L<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 C<run> method using the Modulino approach.
You can find an example of this approach in L<RT::Extension::RepeatTicket>
in the F<bin> directory.

=head2 Patches

If you need to provide patches to RT for any reason, you can put them in
a C<patches> directory. See L</"Changes to RT"> 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<local/plugins> 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<local/plugins/[ext name]> as part of the
installation process. You need to make sure the path under C<html> is correct
since that is installed as-is.

The C<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 C<RT::Extension::Demo>, you should create a
F<RT-Extension-Demo> directory under F<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 C<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 F<share/html/Ticket/Update.html> that looks like this:

    $m->callback( %ARGS, CallbackName => 'AfterWorked', Ticket => $TicketObj );

You look at the F<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 F<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 F<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 C<CallbackName> parameter, name
your file F<Default> 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<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 C<Limit> call to modify search results on
a L<DBIx::SearchBuilder> 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</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 F<Tabs> file itself for examples of adding menu items.
The menu object is a L<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>

=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<hacking> document. We generally accept patches that add new callbacks.

Create a C<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 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<perl Makefile.PL> and you created the F<inc/.author>
directory as described above, a F<README> file will be created for you. You can
now type:

    make manifest

and a F<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 F<inc> directory which contains L<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 F<cpan-upload> utility provided by
L<CPAN::Uploader> or your favorite method of uploading to CPAN.

=cut