Upgrade to 4.0.13
[usit-rt.git] / lib / RT.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
6 #                                          <sales@bestpractical.com>
7 #
8 # (Except where explicitly superseded by other copyright notices)
9 #
10 #
11 # LICENSE:
12 #
13 # This work is made available to you under the terms of Version 2 of
14 # the GNU General Public License. A copy of that license should have
15 # been provided with this software, but in any event can be snarfed
16 # from www.gnu.org.
17 #
18 # This work is distributed in the hope that it will be useful, but
19 # WITHOUT ANY WARRANTY; without even the implied warranty of
20 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
21 # General Public License for more details.
22 #
23 # You should have received a copy of the GNU General Public License
24 # along with this program; if not, write to the Free Software
25 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 # 02110-1301 or visit their web page on the internet at
27 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
28 #
29 #
30 # CONTRIBUTION SUBMISSION POLICY:
31 #
32 # (The following paragraph is not intended to limit the rights granted
33 # to you to modify and distribute this software under the terms of
34 # the GNU General Public License and is only of importance to you if
35 # you choose to contribute your changes and enhancements to the
36 # community by submitting them to Best Practical Solutions, LLC.)
37 #
38 # By intentionally submitting any modifications, corrections or
39 # derivatives to this work, or any other work intended for use with
40 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 # you are the copyright holder for those contributions and you grant
42 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
43 # royalty-free, perpetual, license to use, copy, create derivative
44 # works based on those contributions, and sublicense and distribute
45 # those contributions and any derivatives thereof.
46 #
47 # END BPS TAGGED BLOCK }}}
48
49 use strict;
50 use warnings;
51
52 package RT;
53
54
55 use File::Spec ();
56 use Cwd ();
57
58 use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_Privileged $_Unprivileged $_INSTALL_MODE);
59
60 use vars qw($BasePath
61  $EtcPath
62  $BinPath
63  $SbinPath
64  $VarPath
65  $LexiconPath
66  $PluginPath
67  $LocalPath
68  $LocalEtcPath
69  $LocalLibPath
70  $LocalLexiconPath
71  $LocalPluginPath
72  $MasonComponentRoot
73  $MasonLocalComponentRoot
74  $MasonDataDir
75  $MasonSessionDir);
76
77
78 RT->LoadGeneratedData();
79
80 =head1 NAME
81
82 RT - Request Tracker
83
84 =head1 SYNOPSIS
85
86 A fully featured request tracker package.
87
88 This documentation describes the point-of-entry for RT's Perl API.  To learn
89 more about what RT is and what it can do for you, visit
90 L<https://bestpractical.com/rt>.
91
92 =head1 DESCRIPTION
93
94 =head2 INITIALIZATION
95
96 If you're using RT's Perl libraries, you need to initialize RT before using any
97 of the modules.
98
99 You have the option of handling the timing of config loading and the actual
100 init sequence yourself with:
101
102     use RT;
103     BEGIN {
104         RT->LoadConfig;
105         RT->Init;
106     }
107
108 or you can let RT do it all:
109
110     use RT -init;
111
112 This second method is particular useful when writing one-liners to interact with RT:
113
114     perl -MRT=-init -e '...'
115
116 The first method is necessary if you need to delay or conditionalize
117 initialization or if you want to fiddle with C<< RT->Config >> between loading
118 the config files and initializing the RT environment.
119
120 =cut
121
122 {
123     my $DID_IMPORT_INIT;
124     sub import {
125         my $class  = shift;
126         my $action = shift || '';
127
128         if ($action eq "-init" and not $DID_IMPORT_INIT) {
129             $class->LoadConfig;
130             $class->Init;
131             $DID_IMPORT_INIT = 1;
132         }
133     }
134 }
135
136 =head2 LoadConfig
137
138 Load RT's config file.  First, the site configuration file
139 (F<RT_SiteConfig.pm>) is loaded, in order to establish overall site
140 settings like hostname and name of RT instance.  Then, the core
141 configuration file (F<RT_Config.pm>) is loaded to set fallback values
142 for all settings; it bases some values on settings from the site
143 configuration file.
144
145 In order for the core configuration to not override the site's
146 settings, the function C<Set> is used; it only sets values if they
147 have not been set already.
148
149 =cut
150
151 sub LoadConfig {
152     require RT::Config;
153     $Config = RT::Config->new;
154     $Config->LoadConfigs;
155     require RT::I18N;
156
157     # RT::Essentials mistakenly recommends that WebPath be set to '/'.
158     # If the user does that, do what they mean.
159     $RT::WebPath = '' if ($RT::WebPath eq '/');
160
161     # fix relative LogDir and GnuPG homedir
162     unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) {
163         $Config->Set( LogDir =>
164               File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) );
165     }
166
167     my $gpgopts = $Config->Get('GnuPGOptions');
168     unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
169         $gpgopts->{homedir} = File::Spec->catfile( $BasePath, $gpgopts->{homedir} );
170     }
171
172     return $Config;
173 }
174
175 =head2 Init
176
177 L<Connects to the database|/ConnectToDatabase>, L<initilizes system
178 objects|/InitSystemObjects>, L<preloads classes|/InitClasses>, L<sets
179 up logging|/InitLogging>, and L<loads plugins|/InitPlugins>.
180
181 =cut
182
183 sub Init {
184
185     CheckPerlRequirements();
186
187     InitPluginPaths();
188
189     #Get a database connection
190     ConnectToDatabase();
191     InitSystemObjects();
192     InitClasses();
193     InitLogging();
194     InitPlugins();
195     RT::I18N->Init;
196     RT->Config->PostLoadCheck;
197
198 }
199
200 =head2 ConnectToDatabase
201
202 Get a database connection. See also L</Handle>.
203
204 =cut
205
206 sub ConnectToDatabase {
207     require RT::Handle;
208     $Handle = RT::Handle->new unless $Handle;
209     $Handle->Connect;
210     return $Handle;
211 }
212
213 =head2 InitLogging
214
215 Create the Logger object and set up signal handlers.
216
217 =cut
218
219 sub InitLogging {
220
221     # We have to set the record separator ($, man perlvar)
222     # or Log::Dispatch starts getting
223     # really pissy, as some other module we use unsets it.
224     $, = '';
225     use Log::Dispatch 1.6;
226
227     my %level_to_num = (
228         map( { $_ => } 0..7 ),
229         debug     => 0,
230         info      => 1,
231         notice    => 2,
232         warning   => 3,
233         error     => 4, 'err' => 4,
234         critical  => 5, crit  => 5,
235         alert     => 6,
236         emergency => 7, emerg => 7,
237     );
238
239     unless ( $RT::Logger ) {
240
241         $RT::Logger = Log::Dispatch->new;
242
243         my $stack_from_level;
244         if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) {
245             # if option has old style '\d'(true) value
246             $stack_from_level = 0 if $stack_from_level =~ /^\d+$/;
247             $stack_from_level = $level_to_num{ $stack_from_level } || 0;
248         } else {
249             $stack_from_level = 99; # don't log
250         }
251
252         my $simple_cb = sub {
253             # if this code throw any warning we can get segfault
254             no warnings;
255             my %p = @_;
256
257             # skip Log::* stack frames
258             my $frame = 0;
259             $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
260             my ($package, $filename, $line) = caller($frame);
261
262             $p{'message'} =~ s/(?:\r*\n)+$//;
263             return "[". gmtime(time) ."] [". $p{'level'} ."]: "
264                 . $p{'message'} ." ($filename:$line)\n";
265         };
266
267         my $syslog_cb = sub {
268             # if this code throw any warning we can get segfault
269             no warnings;
270             my %p = @_;
271
272             my $frame = 0; # stack frame index
273             # skip Log::* stack frames
274             $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
275             my ($package, $filename, $line) = caller($frame);
276
277             # syswrite() cannot take utf8; turn it off here.
278             Encode::_utf8_off($p{message});
279
280             $p{message} =~ s/(?:\r*\n)+$//;
281             if ($p{level} eq 'debug') {
282                 return "$p{message}\n";
283             } else {
284                 return "$p{message} ($filename:$line)\n";
285             }
286         };
287
288         my $stack_cb = sub {
289             no warnings;
290             my %p = @_;
291             return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level;
292
293             require Devel::StackTrace;
294             my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] );
295             return $p{'message'} . $trace->as_string;
296
297             # skip calling of the Log::* subroutins
298             my $frame = 0;
299             $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
300             $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/;
301
302             $p{'message'} .= "\nStack trace:\n";
303             while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
304                 $p{'message'} .= "\t$sub(...) called at $filename:$line\n";
305             }
306             return $p{'message'};
307         };
308
309         if ( $Config->Get('LogToFile') ) {
310             my ($filename, $logdir) = (
311                 $Config->Get('LogToFileNamed') || 'rt.log',
312                 $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ),
313             );
314             if ( $filename =~ m![/\\]! ) { # looks like an absolute path.
315                 ($logdir) = $filename =~ m{^(.*[/\\])};
316             }
317             else {
318                 $filename = File::Spec->catfile( $logdir, $filename );
319             }
320
321             unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
322                 # localizing here would be hard when we don't have a current user yet
323                 die "Log file '$filename' couldn't be written or created.\n RT can't run.";
324             }
325
326             require Log::Dispatch::File;
327             $RT::Logger->add( Log::Dispatch::File->new
328                            ( name=>'file',
329                              min_level=> $Config->Get('LogToFile'),
330                              filename=> $filename,
331                              mode=>'append',
332                              callbacks => [ $simple_cb, $stack_cb ],
333                            ));
334         }
335         if ( $Config->Get('LogToScreen') ) {
336             require Log::Dispatch::Screen;
337             $RT::Logger->add( Log::Dispatch::Screen->new
338                          ( name => 'screen',
339                            min_level => $Config->Get('LogToScreen'),
340                            callbacks => [ $simple_cb, $stack_cb ],
341                            stderr => 1,
342                          ));
343         }
344         if ( $Config->Get('LogToSyslog') ) {
345             require Log::Dispatch::Syslog;
346             $RT::Logger->add(Log::Dispatch::Syslog->new
347                          ( name => 'syslog',
348                            ident => 'RT',
349                            min_level => $Config->Get('LogToSyslog'),
350                            callbacks => [ $syslog_cb, $stack_cb ],
351                            stderr => 1,
352                            $Config->Get('LogToSyslogConf'),
353                          ));
354         }
355     }
356     InitSignalHandlers();
357 }
358
359 {   # Work around bug in Log::Dispatch < 2.30, wherein the short forms
360     # of ->warn, ->err, and ->crit do not usefully propagate out, unlike
361     # ->warning, ->error, and ->critical
362     package Log::Dispatch;
363     no warnings 'redefine';
364     sub warn { shift->warning(@_) }
365     sub err  { shift->error(@_) }
366     sub crit { shift->critical(@_) }
367 }
368
369 sub InitSignalHandlers {
370
371 # Signal handlers
372 ## This is the default handling of warnings and die'ings in the code
373 ## (including other used modules - maybe except for errors catched by
374 ## Mason).  It will log all problems through the standard logging
375 ## mechanism (see above).
376
377     $SIG{__WARN__} = sub {
378         # The 'wide character' warnings has to be silenced for now, at least
379         # until HTML::Mason offers a sane way to process both raw output and
380         # unicode strings.
381         # use 'goto &foo' syntax to hide ANON sub from stack
382         if( index($_[0], 'Wide character in ') != 0 ) {
383             unshift @_, $RT::Logger, qw(level warning message);
384             goto &Log::Dispatch::log;
385         }
386         # Return value is used only by RT::Test to filter warnings from
387         # reaching the Test::NoWarnings catcher.  If Log::Dispatch::log() ever
388         # starts returning 'IGNORE', we'll need to switch to something more
389         # clever.  I don't expect that to happen.
390         return 'IGNORE';
391     };
392
393 #When we call die, trap it and log->crit with the value of the die.
394
395     $SIG{__DIE__}  = sub {
396         # if we are not in eval and perl is not parsing code
397         # then rollback transactions and log RT error
398         unless ($^S || !defined $^S ) {
399             $RT::Handle->Rollback(1) if $RT::Handle;
400             $RT::Logger->crit("$_[0]") if $RT::Logger;
401         }
402         die $_[0];
403     };
404 }
405
406
407 sub CheckPerlRequirements {
408     if ($^V < 5.008003) {
409         die sprintf "RT requires Perl v5.8.3 or newer.  Your current Perl is v%vd\n", $^V;
410     }
411
412     # use $error here so the following "die" can still affect the global $@
413     my $error;
414     {
415         local $@;
416         eval {
417             my $x = '';
418             my $y = \$x;
419             require Scalar::Util;
420             Scalar::Util::weaken($y);
421         };
422         $error = $@;
423     }
424
425     if ($error) {
426         die <<"EOF";
427
428 RT requires the Scalar::Util module be built with support for  the 'weaken'
429 function.
430
431 It is sometimes the case that operating system upgrades will replace
432 a working Scalar::Util with a non-working one. If your system was working
433 correctly up until now, this is likely the cause of the problem.
434
435 Please reinstall Scalar::Util, being careful to let it build with your C
436 compiler. Usually this is as simple as running the following command as
437 root.
438
439     perl -MCPAN -e'install Scalar::Util'
440
441 EOF
442
443     }
444 }
445
446 =head2 InitClasses
447
448 Load all modules that define base classes.
449
450 =cut
451
452 sub InitClasses {
453     shift if @_%2; # so we can call it as a function or method
454     my %args = (@_);
455     require RT::Tickets;
456     require RT::Transactions;
457     require RT::Attachments;
458     require RT::Users;
459     require RT::Principals;
460     require RT::CurrentUser;
461     require RT::Templates;
462     require RT::Queues;
463     require RT::ScripActions;
464     require RT::ScripConditions;
465     require RT::Scrips;
466     require RT::Groups;
467     require RT::GroupMembers;
468     require RT::CustomFields;
469     require RT::CustomFieldValues;
470     require RT::ObjectCustomFields;
471     require RT::ObjectCustomFieldValues;
472     require RT::Attributes;
473     require RT::Dashboard;
474     require RT::Approval;
475     require RT::Lifecycle;
476     require RT::Link;
477     require RT::Links;
478     require RT::Article;
479     require RT::Articles;
480     require RT::Class;
481     require RT::Classes;
482     require RT::ObjectClass;
483     require RT::ObjectClasses;
484     require RT::ObjectTopic;
485     require RT::ObjectTopics;
486     require RT::Topic;
487     require RT::Topics;
488
489     # on a cold server (just after restart) people could have an object
490     # in the session, as we deserialize it so we never call constructor
491     # of the class, so the list of accessible fields is empty and we die
492     # with "Method xxx is not implemented in RT::SomeClass"
493
494     # without this, we also can never call _ClassAccessible, because we
495     # won't have filled RT::Record::_TABLE_ATTR
496     $_->_BuildTableAttributes foreach qw(
497         RT::Ticket
498         RT::Transaction
499         RT::Attachment
500         RT::User
501         RT::Principal
502         RT::Template
503         RT::Queue
504         RT::ScripAction
505         RT::ScripCondition
506         RT::Scrip
507         RT::Group
508         RT::GroupMember
509         RT::CustomField
510         RT::CustomFieldValue
511         RT::ObjectCustomField
512         RT::ObjectCustomFieldValue
513         RT::Attribute
514         RT::ACE
515         RT::Link
516         RT::Article
517         RT::Class
518         RT::ObjectClass
519         RT::ObjectTopic
520         RT::Topic
521     );
522
523     if ( $args{'Heavy'} ) {
524         # load scrips' modules
525         my $scrips = RT::Scrips->new(RT->SystemUser);
526         $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' );
527         while ( my $scrip = $scrips->Next ) {
528             local $@;
529             eval { $scrip->LoadModules } or
530                 $RT::Logger->error("Invalid Scrip ".$scrip->Id.".  Unable to load the Action or Condition.  ".
531                                    "You should delete or repair this Scrip in the admin UI.\n$@\n");
532         }
533
534         foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) {
535             local $@;
536             eval "require $class; 1" or $RT::Logger->error(
537                 "Class '$class' is listed in CustomFieldValuesSources option"
538                 ." in the config, but we failed to load it:\n$@\n"
539             );
540         }
541
542     }
543 }
544
545 =head2 InitSystemObjects
546
547 Initializes system objects: C<$RT::System>, C<< RT->SystemUser >>
548 and C<< RT->Nobody >>.
549
550 =cut
551
552 sub InitSystemObjects {
553
554     #RT's system user is a genuine database user. its id lives here
555     require RT::CurrentUser;
556     $SystemUser = RT::CurrentUser->new;
557     $SystemUser->LoadByName('RT_System');
558
559     #RT's "nobody user" is a genuine database user. its ID lives here.
560     $Nobody = RT::CurrentUser->new;
561     $Nobody->LoadByName('Nobody');
562
563     require RT::System;
564     $System = RT::System->new( $SystemUser );
565 }
566
567 =head1 CLASS METHODS
568
569 =head2 Config
570
571 Returns the current L<config object|RT::Config>, but note that
572 you must L<load config|/LoadConfig> first otherwise this method
573 returns undef.
574
575 Method can be called as class method.
576
577 =cut
578
579 sub Config { return $Config || shift->LoadConfig(); }
580
581 =head2 DatabaseHandle
582
583 Returns the current L<database handle object|RT::Handle>.
584
585 See also L</ConnectToDatabase>.
586
587 =cut
588
589 sub DatabaseHandle { return $Handle }
590
591 =head2 Logger
592
593 Returns the logger. See also L</InitLogging>.
594
595 =cut
596
597 sub Logger { return $Logger }
598
599 =head2 System
600
601 Returns the current L<system object|RT::System>. See also
602 L</InitSystemObjects>.
603
604 =cut
605
606 sub System { return $System }
607
608 =head2 SystemUser
609
610 Returns the system user's object, it's object of
611 L<RT::CurrentUser> class that represents the system. See also
612 L</InitSystemObjects>.
613
614 =cut
615
616 sub SystemUser { return $SystemUser }
617
618 =head2 Nobody
619
620 Returns object of Nobody. It's object of L<RT::CurrentUser> class
621 that represents a user who can own ticket and nothing else. See
622 also L</InitSystemObjects>.
623
624 =cut
625
626 sub Nobody { return $Nobody }
627
628 sub PrivilegedUsers {
629     if (!$_Privileged) {
630     $_Privileged = RT::Group->new(RT->SystemUser);
631     $_Privileged->LoadSystemInternalGroup('Privileged');
632     }
633     return $_Privileged;
634 }
635
636 sub UnprivilegedUsers {
637     if (!$_Unprivileged) {
638     $_Unprivileged = RT::Group->new(RT->SystemUser);
639     $_Unprivileged->LoadSystemInternalGroup('Unprivileged');
640     }
641     return $_Unprivileged;
642 }
643
644
645 =head2 Plugins
646
647 Returns a listref of all Plugins currently configured for this RT instance.
648 You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
649
650 =cut
651
652 our @PLUGINS = ();
653 sub Plugins {
654     my $self = shift;
655     unless (@PLUGINS) {
656         $self->InitPluginPaths;
657         @PLUGINS = $self->InitPlugins;
658     }
659     return \@PLUGINS;
660 }
661
662 =head2 PluginDirs
663
664 Takes an optional subdir (e.g. po, lib, etc.) and returns a list of
665 directories from plugins where that subdirectory exists.
666
667 This code does not check plugin names, plugin validitity, or load
668 plugins (see L</InitPlugins>) in any way, and requires that RT's
669 configuration have been already loaded.
670
671 =cut
672
673 sub PluginDirs {
674     my $self = shift;
675     my $subdir = shift;
676
677     require RT::Plugin;
678
679     my @res;
680     foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
681         my $path = RT::Plugin->new( name => $plugin )->Path( $subdir );
682         next unless -d $path;
683         push @res, $path;
684     }
685     return @res;
686 }
687
688 =head2 InitPluginPaths
689
690 Push plugins' lib paths into @INC right after F<local/lib>.
691 In case F<local/lib> isn't in @INC, append them to @INC
692
693 =cut
694
695 sub InitPluginPaths {
696     my $self = shift || __PACKAGE__;
697
698     my @lib_dirs = $self->PluginDirs('lib');
699
700     my @tmp_inc;
701     my $added;
702     for (@INC) {
703         if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
704             push @tmp_inc, $_, @lib_dirs;
705             $added = 1;
706         } else {
707             push @tmp_inc, $_;
708         }
709     }
710
711     # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
712     push @tmp_inc, @lib_dirs unless $added;
713
714     my %seen;
715     @INC = grep !$seen{$_}++, @tmp_inc;
716 }
717
718 =head2 InitPlugins
719
720 Initialize all Plugins found in the RT configuration file, setting up
721 their lib and L<HTML::Mason> component roots.
722
723 =cut
724
725 sub InitPlugins {
726     my $self    = shift;
727     my @plugins;
728     require RT::Plugin;
729     foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
730         $plugin->require;
731         die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
732         push @plugins, RT::Plugin->new(name =>$plugin);
733     }
734     return @plugins;
735 }
736
737
738 sub InstallMode {
739     my $self = shift;
740     if (@_) {
741         my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
742         if ($_[0] and $integrity) {
743             # Trying to turn install mode on but we have a good DB!
744             require Carp;
745             $RT::Logger->error(
746                 Carp::longmess("Something tried to turn on InstallMode but we have DB integrity!")
747             );
748         }
749         else {
750             $_INSTALL_MODE = shift;
751             if($_INSTALL_MODE) {
752                 require RT::CurrentUser;
753                $SystemUser = RT::CurrentUser->new();
754             }
755         }
756     }
757     return $_INSTALL_MODE;
758 }
759
760 sub LoadGeneratedData {
761     my $class = shift;
762     my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
763
764     require "$pm_path/RT/Generated.pm" || die "Couldn't load RT::Generated: $@";
765     $class->CanonicalizeGeneratedPaths();
766 }
767
768 sub CanonicalizeGeneratedPaths {
769     my $class = shift;
770     unless ( File::Spec->file_name_is_absolute($EtcPath) ) {
771
772    # if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}.
773    # otherwise RT.pm will make the source dir(where we configure RT) be the
774    # BasePath instead of the one specified by --prefix
775         unless ( -d $BasePath
776                  && File::Spec->file_name_is_absolute($BasePath) )
777         {
778             my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
779
780      # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'}
781      # is not always absolute
782             $BasePath = File::Spec->rel2abs(
783                           File::Spec->catdir( $pm_path, File::Spec->updir ) );
784         }
785
786         $BasePath = Cwd::realpath($BasePath);
787
788         for my $path (
789                     qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
790                     LocalLibPath LexiconPath LocalLexiconPath PluginPath
791                     LocalPluginPath MasonComponentRoot MasonLocalComponentRoot
792                     MasonDataDir MasonSessionDir/
793                      )
794         {
795             no strict 'refs';
796
797             # just change relative ones
798             $$path = File::Spec->catfile( $BasePath, $$path )
799                 unless File::Spec->file_name_is_absolute($$path);
800         }
801     }
802
803 }
804
805 =head2 AddJavaScript
806
807 helper method to add js files to C<JSFiles> config.
808 to add extra js files, you can add the following line
809 in the plugin's main file:
810
811     RT->AddJavaScript( 'foo.js', 'bar.js' ); 
812
813 =cut
814
815 sub AddJavaScript {
816     my $self = shift;
817
818     my @old = RT->Config->Get('JSFiles');
819     RT->Config->Set( 'JSFiles', @old, @_ );
820     return RT->Config->Get('JSFiles');
821 }
822
823 =head2 AddStyleSheets
824
825 helper method to add css files to C<CSSFiles> config
826
827 to add extra css files, you can add the following line
828 in the plugin's main file:
829
830     RT->AddStyleSheets( 'foo.css', 'bar.css' ); 
831
832 =cut
833
834 sub AddStyleSheets {
835     my $self = shift;
836     my @old = RT->Config->Get('CSSFiles');
837     RT->Config->Set( 'CSSFiles', @old, @_ );
838     return RT->Config->Get('CSSFiles');
839 }
840
841 =head2 JavaScript
842
843 helper method of RT->Config->Get('JSFiles')
844
845 =cut
846
847 sub JavaScript {
848     return RT->Config->Get('JSFiles');
849 }
850
851 =head2 StyleSheets
852
853 helper method of RT->Config->Get('CSSFiles')
854
855 =cut
856
857 sub StyleSheets {
858     return RT->Config->Get('CSSFiles');
859 }
860
861 =head1 BUGS
862
863 Please report them to rt-bugs@bestpractical.com, if you know what's
864 broken and have at least some idea of what needs to be fixed.
865
866 If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
867
868 =head1 SEE ALSO
869
870 L<RT::StyleGuide>
871 L<DBIx::SearchBuilder>
872
873 =cut
874
875 require RT::Base;
876 RT::Base->_ImportOverlays();
877
878 1;