=head1 NAME

RT::StyleGuide - RT Developer Guide

=head1 INTRODUCTION

All code and documentation that is submitted to be included in the RT
distribution should follow the style in this document.  This is not to
try to stifle your creativity, but to make life easier for everyone who
has to work with your code, and to aid those who are not quite sure how
to do something.

The conventions below apply to Perl modules, web programs, and
command-line programs, specifically, but also might apply to some
degree to any Perl code written for use in RT.

Note that these are all guidelines, not unbreakable rules.  If you have
a need to break one of the rules herein, however, then it is
best to first start a discussion in the RT Developers category on the community
forum at L<https://forum.bestpractical.com>.

Note that with much of this document, it is not so much the Right Way as
it is Our Way.  We need to have conventions in order to make life easier
for everyone.  So don't gripe, and just follow it, because you didn't
get a good grade in "Plays Well With Others" in kindergarten and you
want to make up for it now.

We don't always follow this guide.  We are making changes throughout
our code to be in line with it.  But just because we didn't do
it yet, that is no excuse.  Do it anyway.  :-)

This document is subject to change at the whims of the core RT team.

=head1 DEVELOPMENT SETUP

=head2 Setting up a development environment

A quick and easy way to try out changes in RT is to use SQLite and
the inplace layout so you can run code inside your git checkout. Here
is a sample configuration you can use. Put this in a file under
C<etc/RT_SiteConfig.d>, say C<etc/RT_SiteConfig.d/dev.pm>:

    Set( $rtname, 'rt.example.com');
    Set( $CommentAddress, 'rt-comment@example.com' );
    Set( $CorrespondAddress, 'rt@example.com' );
    Set( $Organization, 'example.com' );

    # Set a domain you can hit locally, for example localhost.
    Set( $WebDomain, 'localhost' );
    Set( $WebPort, '8080' );

    # Allows you to run over http without SSL.
    Set($WebSecureCookies, 0);

    # Lets you see all RT log output in your terminal.
    Set( $LogToSTDERR, 'debug');

    # Send email output to files in var so you can look at them.
    Set( $MailCommand, 'mbox');

    # Don't use the Mason cache, so you can see changes immediately.
    Set( $DevelMode, 1);

    # Useful if you need to see raw SQL, but don't leave on because it logs a
    # ton.
    #Set( $StatementLog, 1);

Then run:

    ./configure.ac --with-my-user-group --enable-layout=inplace \
      --enable-developer --with-db-type=SQLite
    make testdeps
    make fixdeps
    make initdb
    sbin/rt-server

You will now be able to access RT at: http://localhost:8080

If the Mason cache is enabled (i.e, you haven't set C<$DevelMode>), then as you
make changes you will likely need to clear the cache:

    rm -rf var/mason_data

You may also need to clear the database as you adjust, test things etc. By
default, the SQLite database file is created in C<var/rt5>. Just delete that
file to clear the test database, and re-run C<make initdb> to start fresh.

You can also run development checkouts with all the databases RT supports.
Change the C<configure.ac> options above to set the database you want to
use. That will change the dependencies installed by C<make fixdeps>, so
you need to re-run that and install the drivers for the new database.


=head1 CODING PRINCIPLES

=head2 Perl Version

We code everything to Perl 5.26.3 or higher.

=head2 Documentation

All modules will be documented using the POD examples in the module
boilerplate.  The function, purpose, use of the module will be
explained, and each public API will be documented with name,
description, inputs, outputs, side effects, etc.

If an array or hash reference is returned, document the size of the
array (including what each element is, as appropriate) and name each key
in the hash.  For complex data structures, map out the structure as
appropriate (e.g., name each field returned for each column from a DB
call; yes, this means you shouldn't use "SELECT *", which you shouldn't
use anyway).

Also document what kind of data returned values are.  Is it an integer,
a block of HTML, a boolean?

All command-line program options should be documented with a
command-line help option showing usage and all options.
Each available function, switch, etc. should be documented, along
with a statement of function, purpose, use of the program.  Do not
use the same options as another program, for a different purpose.

All web templates should be documented with a statement of function,
purpose, and use in a mason comment block.

Any external documents, and documentation for command-line programs and
modules, should be written in POD, where appropriate. From there, they
can be translated to many formats with the various pod2* translators.
Read the perlpod manpage before writing any POD, because although POD is
not difficult, it is not what most people are used to.  It is not a
regular markup language; it is just a way to make easy documentation
for translating to other formats.  Read, and understand, the perlpod
manpage, and ask us or someone else who knows if you have any questions.


=head2 Version

Our distribution versions use tuples, where the first number is the
major revision, the second number is the version, and third
number is the subversion.  Odd-numbered versions are development
versions.  Examples:

        1.0.0           First release of RT 1
        1.0.1           Second release of RT 1.0
        1.0.10          etc.
        1.1.0           First development release of RT 1.2 (or 2.0)
        2.0.0           First release of RT 2

Versions may end in "rc" or "beta" and a number if they are release candidates:

        2.0.0rc1        First release candidate for real 2.0.0


=head2 Comments

All code should be self-documenting as much as possible.  Only include
necessary comments.  Use names like "$ticket_count", so you don't need to
do something like:

        # ticket count
        my $tc = 0;

Include any comments that are, or might be, necessary in order for
someone else to understand the code.  Sometimes a simple one-line
comment is good to explain what the purpose of the following code is
for.  Sometimes each line needs to be commented because of a complex
algorithm.  Read Kernighan & Pike's I<Practice of Programming> about
commenting.  Good stuff, Maynard.


=head2 Warnings and Strict

All code must compile and run cleanly with "use strict" enabled and the
perl "-w" (warnings) option on.  If you must do something that -w or
strict complains about, there are workarounds, but the chances that you
really need to do it that way are remote.

=head2 Lexical Variables

Use only lexical variables, except for special global variables
($VERSION, %ENV, @ISA, $!, etc.) or very special circumstances (see
%HTML::Mason::Commands::session ).  Global variables
for regular use are never appropriate.  When necessary, "declare"
globals with "use vars" or "our()".

A lexical variable is created with my().  A global variable is
pre-existing (if it is a special variable), or it pops into existence
when it is used.  local() is used to tell perl to assign a temporary
value to a variable.  This should only be used with special variables,
like $/, or in special circumstances.  If you must assign to any global
variable, consider whether or not you should use local().

local() may also be used on elements of arrays and hashes, though there
is seldom a need to do it, and you shouldn't.


=head2 Pass by Reference

Arrays and hashes should be passed to and from functions by reference
only.  Note that a list and an array are NOT the same thing.  This
is perfectly fine:

        return($user, $form, $constants);

An exception might be a temporary array of discrete arguments:

        my @return = ($user, $form);
        push @return, $constants if $flag;
        return @return;

Although, usually, this is better (faster, easier to read, etc.):

        if ($flag) {
                return($user, $form, $constants);
        } else {
                return($user, $form);
        }

We need to talk about Class::ReturnValue here.


=head2 Method parameters

If a method takes exactly one mandatory argument, the argument should be
passed in a straightforward manner:

        my $self = shift;
        my $id = shift;

In all other cases, the method needs to take named parameters, usually
using a C<%args> hash to store them:

        my $self = shift;
        my %args = (
            Name => undef,
            Description => undef,
            @_
        );

You may specify defaults to those named parameters instead of using
C<undef> above, as long as it is documented as such.

It is worth noting that the existing RT codebase had not followed this
style perfectly; we are trying to fix it without breaking existing APIs.

=head2 Tests

Modules should provide test code, with documentation on how to use
it.  Test::More makes it easy to create tests. Any code you write
should have a test suite.  Any code you alter should have a test
suite. If a patch comes in without tests, there is something wrong.

When altering code, you must run the test harness before submitting a
patch or committing code to the repository.

See L</TESTING>.

=head2 STDIN/STDOUT

Always report errors using RT->Logger. It's a Log::Dispatch object.
Unlike messages meant for the user, log messages are not to be
internationalized.

There are several different levels (RT->Logger methods) of logging:

=over 4

=item debug

Used for messages only needed during system debugging.

=item info

Should be used to describe "system-critical" events which aren't errors.
Examples: creating users, deleting users, creating tickets, creating queues,
sending email (message id, time, recipients), receiving mail, changing
passwords, changing access control, superuser logins)

=item warn/warning

Used to report issues that need attention, but are not failures. Many systems
will run production at the "warn" level, so only use this for important
events.

=item error

Used for RT-generated failures during execution.

=item crit

Should be used for messages when an action can not be completed due to some
error condition beyond our control.

=back

In the web UI and modules, never print directly to STDERR.  Do not print
directly to STDOUT, unless you need to print directly to the user's console.

In command-line programs, feel free to print to STDERR and STDOUT as
needed for direct console communication. But for actual error reporting,
use the logging API.


=head2 System Calls

Always check return values from system calls, including open(),
close(), mkdir(), or anything else that talks directly to the system.
Perl built-in system calls return the error in $!; some functions in
modules might return an error in $@ or some other way, so read the module's
documentation if you don't know.  Always do something, even if it is
just calling RT->Logger->warning(), when the return value is not what you'd expect.


=head1 STYLE

Much of the style section is taken from the perlstyle manpage.  We make
some changes to it here, but it wouldn't be a bad idea to read that
document, too.

=head2 Terminology

=over 4

=item function vs. sub(routine) vs. method

Just because it is the Perl Way (not necessarily right for all
languages, but the documented terminology in the perl documentation),
"method" should be used only to refer to a subroutine that are object
methods or class methods; that is, these are functions that are used
with OOP that always take either an object or a class as the first
argument. Regular subroutines, ones that are not object or class
methods, are functions.  Class methods that create and return an object
are optionally called constructors.

=item Users

"users" are normally users of RT, the ones hitting the site; if using
it in any other context, specify.
"system users" are user
names on the operating system.  "database users" are the user names in
the database server.  None of these needs to be capitalized.

=back


=head2 Names

Don't use single-character variables, except as iterator variables.

Don't use two-character variables just to spite us over the above rule.

Constants are in all caps; these are variables whose value will I<never>
change during the course of the program.

        $Minimum = 10;          # wrong
        $MAXIMUM = 50;          # right

Other variables are lowercase, with underscores separating the words.
The words used should, in general, form a noun (usually singular),
unless the variable is a flag used to denote some action that should be
taken, in which case they should be verbs (or gerunds, as appropriate)
describing that action.

        $thisVar      = 'foo';  # wrong
        $this_var     = 'foo';  # right
        $work_hard    = 1;      # right, verb, boolean flag
        $running_fast = 0;      # right, gerund, boolean flag

Arrays and hashes should be plural nouns, whether as regular arrays and
hashes or array and hash references.  Do not name references with "ref"
or the data type in the name.

        @stories     = (1, 2, 3);      # right
        $comment_ref = [4, 5, 6];      # wrong
        $comments    = [4, 5, 6];      # right
        $comment     = $comments->[0]; # right

Make the name descriptive.  Don't use variables like "$sc" when you
could call it "$story_count".  See L<"Comments">.

There are several variables in RT that are used throughout the code,
that you should use in your code.  Do not use these variable names for
anything other than how they are normally used, and do not use any
other variable names in their place.  Some of these are:

        $self           # first named argument in object method

Subroutines (except for special cases, like AUTOLOAD and simple accessors)
begin with a verb, with words following to complete the action.  Accessors
don't start with "Get" if they're just the name of the attribute.

Accessors which return an object should end with the suffix Obj.

This section needs clarification for RT.

Words begin with a capital letter.  They
should as clearly as possible describe the activity to be performed, and
the data to be returned.



        Load();         # good
        LoadByName();   # good
        LoadById();     # good

Subroutines beginning with C<_> are special: they are not to be used
outside the current object.  This is not to be enforced by the code
itself, but by someone very big and very scary.

For large for() loops, do not use $_, but name the variable.
Do not use $_ (or assume it) except for when it is absolutely
clear what is going on, or when it is required (such as with
map() and grep()).

        for (@list) {
            print;                      # OK; everyone knows this one
            print uc;                   # wrong; few people know this
            print uc $_;                # better
        }

Note that the special variable C<_> I<should> be used when possible.
It is a placeholder that can be passed to stat() and the file test
operators, that saves perl a trip to re-stat the file.  In the
example below, using C<$file> over for each file test, instead of
C<_> for subsequent uses, is a performance hit.  You should be
careful that the last-tested file is what you think it is, though.

        if (-d $file) {         # $file is a directory
            # ...
        } elsif (-l _) {        # $file is a symlink
            # ...
        }

Package names begin with a capital letter in each word, followed by
lower case letters (for the most part).  Multiple words should be StudlyCapped.

        RT::User                        # good
        RT::Database::MySQL             # proper name
        RT::Display::Provider           # good
        RT::CustomField                 # not so good, but OK

Plugin modules should begin with "RT::Extension::", followed by the name
of the plugin.


=head1 CODE FORMATTING

The RT codebase is more than 25 years old; as such, there are sections
which do not (yet) conform to the guidelines below.  Please attempt to
follow the guidelines, even if the code surrounding your changes does
not yet.

RT includes a F<.perltidyrc> in its top-level which encodes many of
the conventions. When in doubt, use perltidy.

=head2 Indentation and Blank Space

Each level of indentation should be four spaces; hard tabs are forbidden.

No space before a semicolon that closes a statement.

        foo(@bar) ;     # wrong
        foo(@bar);      # right

Line up corresponding items vertically.

        my $foo   = 1;
        my $bar   = 2;
        my $xyzzy = 3;

        open(FILE, $fh)   or die $!;
        open(FILE2, $fh2) or die $!;

        $rot13 =~ tr[abcedfghijklmnopqrstuvwxyz]
                    [nopqrstuvwxyzabcdefghijklm];

        # note we use a-mn-z instead of a-z,
        # for readability
        $rot13 =~ tr[a-mn-z]
                    [n-za-m];

Put blank lines between groups of code that do different things.  Put
blank lines after your variable declarations.  Put a blank line before a
final return() statement.  Put a blank line following a block (and
before, with the exception of comment lines).

An example:

        # this is my function!
        sub foo {
            my $val = shift;
            my $obj = new Constructor;
            my($var1, $var2);

            $obj->SetFoo($val);
            $var1 = $obj->Foo();

            return($val);
        }

        print 1;


=head2 Parentheses

For control structures, there is a space between the keyword and opening
parenthesis.  For functions, there is not.

        for(@list)      # wrong
        for (@list)     # right

        my ($ref)       # wrong
        my($ref)        # right

Be careful about list vs. scalar context with parentheses!

        my @array = ('a', 'b', 'c');
        my($first_element) = @array;            # a
        my($first_element) = ('a', 'b', 'c');   # a
        my $element_count  = @array;            # 3
        my $last_element   = ('a', 'b', 'c');   # c

Always include parentheses after functions, even if there are no arguments.
There are some exceptions, such as list operators (like print) and unary
operators (like undef, delete, uc).

There is no space inside the parentheses, unless it is needed for
readability.

        for ( map { [ $_, 1 ] } @list ) # OK
        for ( @list )                   # not really OK, not horrible

On multi-line expressions, match up the closing parenthesis with either
the opening statement, or the opening parenthesis, whichever works best.
Examples:

        @list = qw(
            bar
            baz
        );                      # right

        if ($foo && $bar && $baz
                 && $buz && $xyzzy) {
            print $foo;
        }

Whether or not there is space following a closing parenthesis is
dependent on what it is that follows.

        print foo(@bar), baz(@buz) if $xyzzy;

Note also that parentheses around single-statement control expressions,
as in C<if $xyzzy>, are optional (and discouraged) C<if> it is I<absolutely>
clear -- to a programmer -- what is going on.  There is absolutely no
need for parentheses around C<$xyzzy> above, so leaving them out enhances
readability.  Use your best discretion.  Better to include them, if
there is any question.

The same essentially goes for Perl's built-in functions, when there is
nothing confusing about what is going on (for example, there is only one
function call in the statement, or the function call is separated by a
flow control operator).  User-supplied functions must always include
parentheses.

        print 1, 2, 3;                          # good
        delete $hash{key} if isAnon($uid);      # good


However, if there is any possible confusion at all, then include the
parentheses.  Remember the words of Larry Wall in the perlstyle manpage:

        When in doubt, parenthesize.  At the very least it will
        let some poor schmuck bounce on the % key in vi.

        Even if you aren't in doubt, consider the mental welfare
        of the person who has to maintain the code after you, and
        who will probably put parens in the wrong place.

So leave them out when it is absolutely clear to a programmer, but if
there is any question, leave them in.


=head2 Braces

(This is about control braces, not hash/data structure braces.)

There is always a space before the opening brace.

        while (<$fh>){  # wrong
        while (<$fh>) { # right

A one-line block may be put on one line, and the semicolon may be
omitted.

        for (@list) { print }

Otherwise, finish each statement with a semicolon, put the keyword and
opening curly on the first line, and the ending curly lined up with the
keyword at the end.

        for (@list) {
            print;
            smell();
        }

Generally, we prefer not "cuddled else":

        if ($foo) {
            print;
        }
        else {
            die;
        }

=head2 Operators

Put space around most operators.  The primary exception is the for
aesthetics; e.g., sometimes the space around "**" is omitted,
and there is never a space before a ",", but always after.

        print $x , $y;  # wrong
        print $x, $y;   # right

        $x = 2 >> 1;    # good
        $y = 2**2;      # ok

Note that "&&" and "||" have a higher precedence than "and" and "or".
Other than that, they are exactly the same.  It is best to use the lower
precedence version for control, and the higher for testing/returning
values.  Examples:

        $bool = $flag1 or $flag2;       # WRONG (doesn't work)
        $value = $foo || $bar;          # right
        open(FILE, $file) or die $!;

        $true  = foo($bar) && baz($buz);
        foo($bar) and baz($buz);

Note that "and" is seldom ever used, because the statement above is
better written using "if":

        baz($buz) if foo($bar);

Most of the time, the confusion between and/&&, or/|| can be alleviated
by using parentheses.  If you want to leave off the parentheses then you
I<must> use the proper operator.  But if you use parentheses -- and
normally, you should, if there is any question at all -- then it doesn't
matter which you use.  Use whichever is most readable and aesthetically
pleasing to you at the time, and be consistent within your block of code.

Break long lines AFTER operators, except for ".", "and", "or", "&&", "||".
Try to keep the two parts to a binary operator (an operator that
has two operands) together when possible.

        print "foo" . "bar" . "baz" .
              "buz";                            # wrong

        print "foo" . "bar" . "baz"
            . "buz";                            # right

        print $foo unless $x == 3 && $y ==
                4 && $z == 5;                   # wrong

        print $foo unless $x == 3 && $y == 4
                       && $z == 5;              # right


=head2 Other

Put space around a complex subscript inside the brackets or braces.

        $foo{$bar{baz}{buz}};   # OK
        $foo{ $bar{baz}{buz} }; # better

In general, use single-quotes around literals, and double-quotes
when the text needs to be interpolated.

It is OK to omit quotes around names in braces and when using
the => operator, but be careful not to use a name that doubles as
a function; in that case, quote.

        $what{'time'}{it}{is} = time();

When making compound statements, put the primary action first.

        open(FILE, $fh) or die $!;      # right
        die $! unless open(FILE, $fh);  # wrong

        print "Starting\n" if $verbose; # right
        $verbose && print "Starting\n"; # wrong


Use here-docs instead of repeated print statements.

                print <<EOT;
        This is a whole bunch of text.
        I like it.  I don't need to worry about messing
        with lots of print statements and lining them up.
        EOT

Just remember that unless you put single quotes around your here-doc
token (<<'EOT'), the text will be interpolated, so escape any "$" or "@"
as needed.


=head1 INTERNATIONALIZATION

RT has been translated into several dozen languages. We use Launchpad
( https://translations.launchpad.net/rt ) to crowdsource our
translations into C<po> files. RT uses L<Locale::Maketext> to
localize its user interface.

Your first stop on this magical journey of internationalization
is L<Locale::Maketext::TPJ13>, which explains the whys of
L<Locale::Maketext>. RT uses most of the features developed in that
article.

=head2 Using loc() and /l

Strings that are displayed to users should be passed through the
C<loc("...")> function or the C<< <&|/l&>...</&> >> Mason template.
C<loc> and C</l> both take parameters, which are used in place of
string interpolation (much like C<sprintf>). It's acceptable to use
HTML in C</l> calls, especially for bold and emphasis. However, you
should limit the amount of HTML that translators must keep exactly
correct, which means avoid including tags that wrap the entire
translatable string, especially C<< <p> >>.

    <p><&|/l, $button &>Do <em>not</em> click [_1]</&></p> # ok

    <&|/l, $button &><p>Do <em>not</em> click [_1]</p></&> # not ok

In a few places in RT we also pass HTML as parameters to C<loc()>
so that translators do not have to reproduce it exactly, and we can
also change it more freely. For example:

    <&|/l,
        '<a href="http://www.gnu.org/licenses/gpl-2.0.html">',
        '</a>',
    &>Distributed under [_1]version 2 of the GNU GPL[_2].</&>

=head2 Translation Extraction

F<devel/tools/extract-message-catalog> looks for C<loc("...")> and
C<< <&|/l&>...</&> >> in our source code to pick out translatable
strings, clean them up, and put them into F<share/po> files. We use
our C<.po> files not only to populate L<Locale::Maketext>'s lexicons,
but also to sync new translatable strings and translations with
Launchpad. This Launchpad sync is typically done early during the
freeze of RC releases to give our volunteer translators time to
translate all the new strings which, because of the RC freeze, won't
continue changing.

=head2 Translating Hard-coded Strings

Because C<loc()> and C</l> are used to generate strings for human
eyes, they generally must be used "close to the browser". These are
directly in Mason templates, or in functions that return text that
will be passed through Mason. However, in many places in RT we have
hard-coded strings which need translations. For example, the C<$RIGHTS>
hash in F<lib/RT/Queue.pm> maps rights' names (which must be
translatable) to their descriptions (which also must be translatable).
However, when we're declaring such structures, we do not want to
translate them straight away. RT uses English internally, including
in its web forms, so we do not want to localize rights' names except
for display, otherwise things might break weirdly when you check
if a user has the "Superusuario" right. Furthermore, when we're
declaring such data structures at compile time, there is no current
user to select which language to use for localization. Thus, we
cannot call C<loc()> when declaring C<$RIGHTS> and other similar
places.

For this reason, F<devel/tools/extract-message-catalog> lets you
denote translatable strings with comments. That's what the C<#loc_pair>
comments in the C<$RIGHTS> hash in F<lib/RT/Queue.pm> indicate.
Since we have those comments, our toolchain will put the rights'
names and descriptions into F<share/po> files, which enables
translation by our lovely volunteers. Later on, when RT displays
information about rights in the web UI, we'll pass the right's name
through C<loc>, and L<Locale::Maketext> will then be able to find
our "Superusuario". So although we never used a literal
C<loc("SuperUser")>, we still get its effects thanks to the
C<#loc_pair> comments and using C<loc($RightName)>.

=head2 Translation Comment Markers

C<#loc_pair> is used for declaring that both the key and value
of a particular C<< key => value >> pair are translatable. There
are other markers that you can use.

C<#loc> is used for declaring that a particular string is translatable.
Its parsing is pretty strict so you can use it to declare that only
the value of a particular C<< key => value >> pair is translatable.

C<#loc_left_pair> is used for declaring that the I<key> of a
particular C<< key => value >> pair is translatable. This is of
very limited usefulness.

C<#loc_right_pair> does NOT exist. C<#loc> works in such cases since its
parser does not extend beyond the string at the end of a line.  However,
if the string is I<not> at the end of the line, C<#loc{word}> declares
that the value associated with the key I<word> (earlier on the same
line) is to be loc'd.  This is useful for inline hashes:

    # Note the string "baz" is to be loc'd
    foo => { bar => "baz", troz => "zort" },  # loc{bar}

=head2 String Extraction in Web Templates

=over 4

=item Web templates

Templates should use the /l filtering component to call the localisation
framework

The string              Foo!

Should become           <&|/l&>Foo!</&>

All newlines should be removed from localized strings, to make it easy to
grep the codebase for strings to be localized

The string              Foo
                        Bar
                        Baz

Should become           <&|/l&>Foo Bar Baz</&>


Variable substitutions should be moved to Locale::MakeText format

The string              Hello, <%$name %>

should become           <&|/l, $name &>Hello, [_1]</&>


Multiple variables work just like single variables

The string              You found <%$num%> tickets in queue <%$queue%>

should become           <&|/l, $num, $queue &>You found [_1] tickets in queue [_2]</&>

When subcomponents are called in the middle of a phrase, they need to be escaped
too:

The string               <input type="submit" value="New ticket in">&nbsp<& /Elements/SelectNewTicketQueue&>

should become           <&|/l, $m->scomp('/Elements/SelectNewTicketQueue')&><input type="submit" value="New ticket in">&nbsp;[_1]</&>

The string      <& /Widgets/TitleBoxStart, width=> "40%", titleright => "RT $RT::VERSION for   RT->Config->Get('rtname')", title => 'Login' &>

should become   <& /Widgets/TitleBoxStart,
                        width=> "40%",
                        titleright => loc("RT [_1] for [_2]",$RT::VERSION, RT->Config->Get('rtname')),
                        title => loc('Login'),
                &>

=item Library code

Within RT's core code, every module has a localization handle available through the 'loc' method:

The code        return ( $id, "Queue created" );

should become   return ( $id, $self->loc("Queue created") );

When returning or localizing a single string, the "extra" set of parenthesis () should be omitted.

The code        return ("Subject changed to ". $self->Data );

should become    return $self->loc( "Subject changed to [_1]", $self->Data );

It is important not to localize  the names of rights or statuses within RT's core, as there is logic that depends on them as string identifiers.  The proper place to localize these values is when they're presented for display in the web or commandline interfaces.


=back


=head1 TESTING

=head2 Test suite

RT comes with a fairly complete test suite. The C<*-trunk> and
C<master> branches are expected to always be passing all tests.  While
it is acceptable to break tests in an intermediate commit, a branch
which does not pass tests will not be merged.  Ideally, commits which
fix a bug should also include a test case which fails before the fix
and succeeds after.

To run RT's test suite, first set environment variables to a database
user and password which can create and drop databases:

    export RT_DBA_USER=root
    export RT_DBA_PASSWORD=

You'll need to configure RT and make sure you have all the dependencies
before running tests.  To do this in place without installing:

    ./configure.ac --with-my-user-group --enable-layout=inplace --enable-developer
    make testdeps
    make fixdeps

Adjust the relevant database options as necessary if you want to test on
PostgreSQL, Oracle, or SQLite.  The default is MySQL/MariaDB.

To run the test suite:

    make test

If you want to run only a subset of the tests, you can use Perl's standard
C<prove> utility.

    prove -l t/api/ticket.t t/web/ticket_display.t

=head2 Parallel Tests

If you have multiple processors, you can run the test suite in parallel,
which will be significantly faster:

    make test-parallel

If you want to run a subset of the tests in parallel, you can pass the C<-j>
flag to C<prove> with the number of processes to use. Make sure to also set the
C<RT_TEST_PARALLEL> environment variable to a true value to tell RT you're
running the tests in parallel.

    RT_TEST_PARALLEL=1 prove -l -j4 t/customfields/*.t

To run playwright tests sequentially and others in parallel:

    RT_TEST_PARALLEL=1 prove -l -j4 --rules='seq=t/playwright/*.t' --rules='par=**' t

=head2 Playwright Tests

Starting with version 6.0.3, RT uses Playwright for browser-based testing.
Playwright allows you to test functionality with a virtual browser,
making it possible to test behavior that happens only in the browser,
specifically javascript features. This is also helpful for confirming
htmx interactions. Selenium was used in previous versions.

Playwright tests are located in the C<t/playwright/> directory.

=head3 Prerequisites

You need Node.js and the Perl Playwright module installed:

    # On Mac, we had issues when installing via brew. Installing
    # nvm seems to be the current recommended approach.
    # See https://nodejs.org/en/download for details.

    # Install the Perl Playwright module
    cpanm Playwright

After installing the Perl module, install the browser drivers:

    npx playwright install firefox

You can also install other browsers (chromium, webkit) if needed.

=head3 Running Playwright Tests

Run all playwright tests:

    prove -l t/playwright/

Run a specific test:

    prove -lv t/playwright/ticket_create.t

=head3 Environment Variables

=over 4

=item RT_TEST_PLAYWRIGHT_HEADLESS

Set to 0 to run browser in non-headless mode for debugging. Default: 1

    RT_TEST_PLAYWRIGHT_HEADLESS=0 prove -lv t/playwright/ticket_create.t

When set to 0, you can see the browser window and watch tests execute, which
is helpful for debugging test failures or understanding page interactions.

=item RT_TEST_PLAYWRIGHT_BROWSER

Browser type to use: 'firefox', 'chromium', or 'webkit'. Default: firefox

    RT_TEST_PLAYWRIGHT_BROWSER=chromium prove -lv t/playwright/ticket_create.t

Firefox is the default as it has been tested most extensively with RT.

=back

=head2 Crypt Tests

Tests for RT's GnuPG and SMIME features have some additional dependencies and
environment variables to enable or disable tests.

To run the GPG tests, you need a working C<gpg> executable of a version supported
by L<GnuPG::Interface>. If your environment isn't set up for this, you can set
C<SKIP_GPG_TESTS> to true to skip tests. The tests also check for C<gpg> and don't
run if it's not found.

Some of the SMIME tests access an internet resource to check certificates. This
is disabled by default since some test systems don't have internet access. To
run these tests, set C<RT_TEST_SMIME_REVOCATION> to true.

=head2 Tests with Docker

RT has a Dockerfile that can be used to run the test suite for some configurations.
To run it you need a system that can run docker natively or run the docker desktop
client. Once installed, you can look in the C<.github/workflows/test-all.yml> file
for the set of commands to run to kick off the tests inside docker containers. Run
all commands in the "Build RT" and "Run RT tests" sections, replacing any variables
with appropriate values for your system.

You can replace $GITHUB_WORKSPACE with your current working directory if you are
in a branch. Note that the docker commands in the configuration mount
your directory inside the docker container. This means any stray files you have
in your working directory will also be visible to the processes inside the
docker container. This can cause test failures if unexpected files are
there.


=head1 VERSION CONTROL

RT's source code is stored in a C<git> repository.  The RT source repository
is available via git from GitHub; you can browse it at
L<http://github.com/bestpractical/rt/> or obtain a local copy via:

    git clone git://github.com/bestpractical/rt.git

=head2 Branch Organization

The bleeding-edge development happens in the C<master> branch.  When a
major release is anticipated, a "trunk" branch will be branched from
this -- for example, C<4.0-trunk>.  This will allow the trunk to
stabilize while feature development continues on C<master>.
Additionally, as a release is impending for a particular series, a
release engineering branch will be created, named, for example
C<4.0.0-releng>.

New feature development should always be based off of the C<master>
branch.  Branches to fix bugs should be based off of whichever trunk the
bug was first found in.  If you found the bug in your RT 4.0.0 install,
you'd branch from 4.0-trunk.

Branches should be named based on the trunk they are branched
from -- which is to say, the earliest branch they might be merged into.
For example, a bugfix branched from C<4.0-trunk> might be named
C<4.0/fail-taint-mode-early>.  A feature branched from C<master> when
there exists a C<4.0-trunk> but no C<4.2-trunk> might be named
C<4.2/rename-LogToScreen>.  For consistency, branches should use dashes,
not underscores, to separate words.  Branches which are destined for
4.2, but which are branched from 4.0 (to provide for easy extraction as
a 4.0 extension) should be named 4.2-on-4.0/branch-name.

=head2 Code Review

Branches should be reviewed by another developer before being merged.
Reviewers should make sure that the branch accomplishes what it claims
to, and does not introduce any unwanted behavior in doing so.  Commit
messages explain the B<why> as much as the B<what> of each commit, and
not include extraneous changes.

=head2 Git Config Files

The RT repo has a C<.gitattributes> file with a line that looks for a diff
configuration for minified javascript. To help git generate readable
diffs, you can use a configuration like this after installing the
C<js-beautify> utility.

[diff "minjs"]
    textconv = js-beautify
    cachetextconv = true

=head2 Git Quickstart

The process below describes how to get a copy of an RT repo,
modify it, and submit your changes as a patch. You can also create
an account on GitHub, fork RT, and submit a PR. GitHub has documentation
on this process: L<https://help.github.com/articles/fork-a-repo/>.

=over

=item 1.

You will first need to obtain a copy of git; this is accomplished via
C<sudo yum install git> in RedHat and derivatives, or C<sudo apt-get
install git> for Debian or Ubuntu.

=item 2.

Next, obtain a copy of the RT source from git:

    git clone git://github.com/bestpractical/rt.git
    cd rt

=item 3.

Configure git to know your name and email address; git uses these when
it makes commits.

    git config user.email your.email@example.com
    git config user.name Examp L. Name

=item 4.

Switch to the appropriate point to base your work on; this is generally
C<origin/> followed by the major version, followed by C<-trunk>.  For
example, if your bug was observed in version 3.8.9, you would choose
C<origin/3.8-trunk>; if it was in 4.0.0, you would choose
C<origin/4.0-trunk>.  New features should be based on C<origin/master>.

    git checkout --track origin/4.0-trunk

=item 5.

Give your branch a name based on what you are attempting to accomplish.
We suggest that branch names be lower-case and separate words with
dashes, but this branch name is purely for your own reference.

    git branch -m gnupg-encryption

=item 6.

Edit the source tree to make your changes.  A few commands you may find
useful in doing so are listed below.

To see what files you have changed:

    git status

To see a line-by-line list of changes:

    git diff

To revert a file to the original version:

    git checkout path/to/file

To revert only individual parts of a file:

    git checkout -p path/to/file

=item 7.

Check that you have no extraneous changes using C<git diff>, then commit
your changes:

    git commit -a

You will be prompted to type your commit message.  The first line should
be a short (E<lt> 80 character) summary of the changes, followed by a
blank line, followed by a longer description, if necessary.  The commit
message should not simply restate the diff of which lines were added and
subtracted, but should rather explain B<what> those changes accomplish,
and B<why> they are desired.

If your changes are easily split into multiple components, you may wish
to split your changes into more than one commit; simply return to step 6
and repeat the with the next related change.  If your changes are B<not>
related to each other, you should submit them separately; finish step 9,
then start over from step 4.

=item 8.

Save your commits to patch files:

    git format-patch @{u}

This will print out the names of the files as it creates them.

=item 9.

Attach these files to an email using your standard email client, and
send it to C<rt-bugs@bestpractical.com>. This will create a ticket in
our public RT instance at L<https://issues.bestpractical.com>.

=back

If you have another bug or feature to implement, simply restart the
process at step 4.


=head1 CODING PROCEDURE

This is for new programs, modules, specific APIs, or anything else.

=over 4

=item Create a topic in RT Developers on the Forum

We may know of a better way to approach the problem, or know of an
existing way to deal with it, or know someone else is working on it.
This is mostly informal, but a fairly complete explanation for the need
and use of the code should be provided.


=item Present specs in RT Developers

The complete proposed API  should be submitted for
discussion.  For web and command-line programs, present the
functionality and interface (op codes, command-line switches, etc.).

The best way to do this is to take the documentation portion of the
boilerplate and fill it in.  You can make changes later if necessary,
but fill it in as much as you can.



=item Prepare for code review

When you are done, the code will undergo a code review by a member of
the core team, or someone picked by the core team.  This is not to
belittle you (that's just a nice side effect), it is to make sure that
you understand your code, that we understand your code, that it won't
break other code, that it follows the documentation and existing
proposal.  It is to check for possible optimizations or better ways of
doing it.

Note that all code is expected to follow the coding principles and style
guide contained in this document.


=item Finish it up

After the code is done (possibly going through multiple code reviews),
submit your updates as a pull request on GitHub. If you don't have a GitHub
account, you can generate patches and send email to rt-bugs@bestpractical.com
which will create a ticket in our public issue tracker at
L<https://issues.bestpractical.com>.

=back


=head1 BUG REPORTS, PATCHES

Use rt-bugs@bestpractical.com for I<any> bug that is not being fixed
immediately.  If it is not in RT, there is a good chance it will not be
dealt with.

Send patches to rt-bugs@bestpractical.com, too.  Use C<diff -u> for
patches.

=head1 SCHEMA DESIGN

RT uses a convention to denote the foreign key status in its tables.
The rule of thumb is:

=over 4

=item When it references to another table, always use the table name

For example, the C<Template> field in the C<Scrips> table refers to
the C<Id> of the same-named C<Template> table.

=item Otherwise, always use the C<Id> suffix

For example, the C<ObjectId> field in the C<ACL> table can refer
to any object, so it has the C<Id> suffix.

=back

There are some legacy fields that did not follow this rule, namely
C<ACL.PrincipalId>, C<GroupMembers.GroupId> and C<Attachments.TransactionId>,
but new tables are expected to be consistent.


=head1 EXTENDING RT CLASSES

=head2 The Overlay mechanism

RT's classes allow "overlay" methods to be placed into files named
F<Filename_Vendor.pm> and F<Filename_Local.pm>. _Vendor is for 3rd-party
vendor add-ons, while _Local is for site-local customizations.

These overlay files can contain new subs or subs to replace existing subs in this module.

Each of these files should begin with the line:

   no warnings qw(redefine);

so that perl does not kick and scream when you redefine a subroutine or variable in your overlay.

Some common ways that overlays are used:

=head3 Adding Methods

Create a file named F<Classname_Local.pm> as appropriate (like F<User_Local.pm>)
in F<rt_base_dir/local/lib/RT/>.

    no warnings qw(redefine);

    sub MyNewMethod {
        my $self = shift;
        ...
    }

    1;

=head3 Modifying Methods


Create a file with a F<_Local.pm> suffix as appropriate in the same
F<local/lib> tree mentioned above. Copy the method you need to
modify from the original RT version of the file and paste it into your
local version. Then modify the code to behave the way you need it to.

When changing code in this way, make note of incoming values and especially
return values since other code likely expects the existing method to return
values a certain way.

When copying code for modification, do not place the C<_ImportOverlays> call
from the original at the bottom of your modified file.

    no warnings qw(redefine);

    sub ExistingMethod {
        ... existing RT code
        ... my special changes
        ... existing RT code
    }

    1;

=head3 Hooking Methods

Set up your local file the same way as in B<Modifying Methods> described
above. You'll need to save the original method call before you redefine it,
and then call it at the head or tail of your code:

    no warnings qw(redefine);

    # This should be the same class we are overlaying here
    my $original_method = \&RT::Class::MethodToHook;

    sub MethodToHook {
        my $self = shift;
        ...
        ... my special code
        ...
        # Call the original method at the tail of our extra code:
        &$original_method( $args );
    }

    1;

B<Remember!> If you modify existing core RT methods, you will need to update your
local modifications when you upgrade the base RT code so that it matches.

=head1 WEB INTERFACE

=head2 Using TitleBox for Portlets

RT uses the C<TitleBox> widget to create consistent portlet-style UI
components throughout the interface. A titlebox renders as a Bootstrap
card with a header (title) and body section. This section explains how
to use titleboxes effectively, including integration with HTMX for
lazy loading.

=head3 Basic Usage

The simplest titlebox wraps content in a card with a title:

    <&| /Widgets/TitleBox, title => loc('My Portlet'), class => 'my-portlet' &>
      <p>Content goes here</p>
    </&>

Common parameters:

=over 4

=item title

The text displayed in the card header. Use C<loc()> for internationalization.

=item class

CSS class(es) added to the outer titlebox div. Used for styling and targeting
with CSS or JavaScript.

=item title_class

CSS class(es) added to the card header element.

=item hideable

Boolean (default 1). When true, displays a toggle to collapse/expand the
portlet.

=item rolledup

Boolean (default 0). When true, the portlet starts in collapsed state.

=item titleright_raw

Raw HTML to display on the right side of the title bar, commonly used for
action links or icons.

=item icons_ref

Reference to an array of hashes defining icon buttons in the title bar.
Each hash can contain: C<icon_name>, C<tooltip_text>, C<icon_href>, C<class>,
C<modal>, and HTMX attributes.

=item content_body_class

CSS class(es) added to the inner card-body div. Useful for adjusting padding
in special layouts (see L</Card Body Spacing> below).

=item data

Hash reference of data attributes to add to the titlebox element.

=back

=head3 Card Body Spacing

RT's titlebox uses Bootstrap's card component with customized padding.
The card body has a default top padding of C<0.5rem> to create balanced
spacing below the title.

For forms and other content using the C<LabeledValue> template, RT's CSS
automatically detects when the first visible element has the C<mt-2> class
and removes the card body's top padding. This prevents double spacing since
C<LabeledValue> already includes top margin.

B<Important>: This automatic adjustment means that adding C<mt-2> to your
first element will not increase spacing from the title - instead, the CSS
removes the card body padding to maintain consistent spacing. This is
intentional behavior that ensures forms render correctly.

For special layouts that don't use the C<mt-2> pattern (such as multi-column
layouts with their own spacing), you can explicitly control the padding:

    <&| /Widgets/TitleBox, title => loc('Custom Layout'),
        content_body_class => 'pt-0',
    &>
      <div class="row">
        <div class="col-6">...</div>
        <div class="col-6">...</div>
      </div>
    </&>

The CSS also automatically reduces padding for content starting with tables
or accordions, which have their own internal spacing.

=head3 Using LabeledValue for Forms

The C<LabeledValue> template provides consistent form field layout within
titleboxes:

    <&| /Widgets/TitleBox, title => loc('Settings') &>
      <&| /Elements/LabeledValue, Label => loc('Name') &>
        <input type="text" class="form-control" name="Name" />
      </&>
      <&| /Elements/LabeledValue, Label => loc('Description') &>
        <textarea class="form-control" name="Description"></textarea>
      </&>
    </&>

Each C<LabeledValue> creates a row with the C<mt-2> class, providing
consistent vertical spacing between fields.

=head3 HTMX Integration for Lazy Loading

Titleboxes support HTMX-based lazy loading, where the card header renders
immediately and the body content loads asynchronously. This improves initial
page load time for pages with many portlets.

To enable lazy loading, use C<htmx_load> with an C<htmx_get> or C<htmx_post>
URL:

    <&| /Widgets/TitleBox,
        title => loc('Quick Create'),
        htmx_load => 1,
        htmx_get => RT->Config->Get('WebPath') . '/Views/Component/QuickCreate',
    &>
    </&>

When C<htmx_load> is enabled:

=over 4

=item *

The titlebox renders immediately with a loading spinner in the body

=item *

HTMX issues a request to the specified URL when the element becomes visible

=item *

The response replaces the spinner with the actual content

=item *

Content returned from C</Views/> paths automatically strips the titlebox
wrapper to avoid nested cards

=back

For content under C</Views/>, the C<TitleBox> component automatically detects
the path and returns only the inner content without the card wrapper. This
allows the same Mason component to work both in the initial page render and
as an HTMX response.

To force full titlebox rendering even for a C</Views/> path (useful for
components that might be loaded in different contexts), explicitly set
C<htmx_load =E<gt> 0>:

    <&| /Widgets/TitleBox, title => loc('My Content'), htmx_load => 0 &>
      ...
    </&>

=head3 Refreshing Content with HTMX

Titlebox content can refresh in response to custom HTMX events. Add
C<hx-trigger> to an inner div to listen for events:

    <&| /Widgets/TitleBox, title => loc('Ticket Info') &>
      <div hx-trigger="ticketBasicsChanged from:body"
           hx-get="/Views/Ticket/ShowBasics?id=123"
           hx-swap="innerHTML">
        <& /Ticket/Elements/ShowBasics, ... &>
      </div>
    </&>

When JavaScript dispatches a C<ticketBasicsChanged> event on the body, HTMX
automatically refreshes the inner div content.

=head3 Example: Complete Portlet with Inline Edit

Here's a more complete example showing a portlet with inline editing
capability:

    <&| /Widgets/TitleBox,
        title => loc('The Basics'),
        class => 'ticket-info-basics',
        titleright_raw => $can_modify
            ? $m->scomp('/Elements/InlineEditLink')
            : '',
        data => { 'inline-edit-behavior' => 'link' },
    &>
      <div class="inline-edit-display"
           hx-trigger="ticketBasicsChanged from:body"
           hx-get="/Views/Ticket/ShowBasics?id=<% $id %>">
        <& /Ticket/Elements/ShowBasics, ... &>
      </div>
      <form hx-post="/Helpers/TicketUpdate"
            hx-swap="none"
            class="inline-edit">
        <input type="hidden" name="id" value="<% $id %>" />
        <& /Ticket/Elements/EditBasics, ... &>
        <div class="row mt-2">
          <div class="col-12 text-end">
            <input type="submit" class="btn btn-primary"
                   value="<&|/l&>Save</&>" />
          </div>
        </div>
      </form>
    </&>


=head1 TO DO

Talk about DBIx::SearchBuilder

Talk about mason
        component style
        cascading style sheets

Talk about adding a new translation

Talk more about logging

=cut
