Merge branch 'master' of https://github.com/jgoodman/Freeside
[freeside.git] / rt / lib / RT.pm
1 # BEGIN BPS TAGGED BLOCK {{{
2 #
3 # COPYRIGHT:
4 #
5 # This software is Copyright (c) 1996-2014 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     shift if @_%2; # code is inconsistent about calling as method
185     my %args = (@_);
186
187     CheckPerlRequirements();
188
189     InitPluginPaths();
190
191     #Get a database connection
192     ConnectToDatabase();
193     InitSystemObjects();
194     InitClasses(%args);
195     InitLogging(%args);
196     InitPlugins();
197     RT::I18N->Init;
198     RT->Config->PostLoadCheck;
199
200 }
201
202 =head2 ConnectToDatabase
203
204 Get a database connection. See also L</Handle>.
205
206 =cut
207
208 sub ConnectToDatabase {
209     require RT::Handle;
210     $Handle = RT::Handle->new unless $Handle;
211     $Handle->Connect;
212     return $Handle;
213 }
214
215 =head2 InitLogging
216
217 Create the Logger object and set up signal handlers.
218
219 =cut
220
221 sub InitLogging {
222
223     my %arg = @_;
224
225     # We have to set the record separator ($, man perlvar)
226     # or Log::Dispatch starts getting
227     # really pissy, as some other module we use unsets it.
228     $, = '';
229     use Log::Dispatch 1.6;
230
231     my %level_to_num = (
232         map( { $_ => } 0..7 ),
233         debug     => 0,
234         info      => 1,
235         notice    => 2,
236         warning   => 3,
237         error     => 4, 'err' => 4,
238         critical  => 5, crit  => 5,
239         alert     => 6,
240         emergency => 7, emerg => 7,
241     );
242
243     unless ( $RT::Logger ) {
244
245         $RT::Logger = Log::Dispatch->new;
246
247         my $stack_from_level;
248         if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) {
249             # if option has old style '\d'(true) value
250             $stack_from_level = 0 if $stack_from_level =~ /^\d+$/;
251             $stack_from_level = $level_to_num{ $stack_from_level } || 0;
252         } else {
253             $stack_from_level = 99; # don't log
254         }
255
256         my $simple_cb = sub {
257             # if this code throw any warning we can get segfault
258             no warnings;
259             my %p = @_;
260
261             # skip Log::* stack frames
262             my $frame = 0;
263             $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
264             my ($package, $filename, $line) = caller($frame);
265
266             $p{'message'} =~ s/(?:\r*\n)+$//;
267             return "[$$] [". gmtime(time) ."] [". $p{'level'} ."]: "
268                 . $p{'message'} ." ($filename:$line)\n";
269         };
270
271         my $syslog_cb = sub {
272             # if this code throw any warning we can get segfault
273             no warnings;
274             my %p = @_;
275
276             my $frame = 0; # stack frame index
277             # skip Log::* stack frames
278             $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
279             my ($package, $filename, $line) = caller($frame);
280
281             # syswrite() cannot take utf8; turn it off here.
282             Encode::_utf8_off($p{message});
283
284             $p{message} =~ s/(?:\r*\n)+$//;
285             if ($p{level} eq 'debug') {
286                 return "[$$] $p{message} ($filename:$line)\n";
287             } else {
288                 return "[$$] $p{message}\n";
289             }
290         };
291
292         my $stack_cb = sub {
293             no warnings;
294             my %p = @_;
295             return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level;
296
297             require Devel::StackTrace;
298             my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] );
299             return $p{'message'} . $trace->as_string;
300
301             # skip calling of the Log::* subroutins
302             my $frame = 0;
303             $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
304             $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/;
305
306             $p{'message'} .= "\nStack trace:\n";
307             while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
308                 $p{'message'} .= "\t$sub(...) called at $filename:$line\n";
309             }
310             return $p{'message'};
311         };
312
313         if ( $Config->Get('LogToFile') ) {
314             my ($filename, $logdir) = (
315                 $Config->Get('LogToFileNamed') || 'rt.log',
316                 $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ),
317             );
318             if ( $filename =~ m![/\\]! ) { # looks like an absolute path.
319                 ($logdir) = $filename =~ m{^(.*[/\\])};
320             }
321             else {
322                 $filename = File::Spec->catfile( $logdir, $filename );
323             }
324
325             unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
326                 # localizing here would be hard when we don't have a current user yet
327                 die "Log file '$filename' couldn't be written or created.\n RT can't run.";
328             }
329
330             require Log::Dispatch::File;
331             $RT::Logger->add( Log::Dispatch::File->new
332                            ( name=>'file',
333                              min_level=> $Config->Get('LogToFile'),
334                              filename=> $filename,
335                              mode=>'append',
336                              callbacks => [ $simple_cb, $stack_cb ],
337                            ));
338         }
339         if ( $Config->Get('LogToScreen') ) {
340             require Log::Dispatch::Screen;
341             $RT::Logger->add( Log::Dispatch::Screen->new
342                          ( name => 'screen',
343                            min_level => $Config->Get('LogToScreen'),
344                            callbacks => [ $simple_cb, $stack_cb ],
345                            stderr => 1,
346                          ));
347         }
348         if ( $Config->Get('LogToSyslog') ) {
349             require Log::Dispatch::Syslog;
350             $RT::Logger->add(Log::Dispatch::Syslog->new
351                          ( name => 'syslog',
352                            ident => 'RT',
353                            min_level => $Config->Get('LogToSyslog'),
354                            callbacks => [ $syslog_cb, $stack_cb ],
355                            stderr => 1,
356                            $Config->Get('LogToSyslogConf'),
357                          ));
358         }
359     }
360     InitSignalHandlers(%arg);
361 }
362
363 {   # Work around bug in Log::Dispatch < 2.30, wherein the short forms
364     # of ->warn, ->err, and ->crit do not usefully propagate out, unlike
365     # ->warning, ->error, and ->critical
366     package Log::Dispatch;
367     no warnings 'redefine';
368     sub warn { shift->warning(@_) }
369     sub err  { shift->error(@_) }
370     sub crit { shift->critical(@_) }
371 }
372
373 sub InitSignalHandlers {
374
375     my %arg = @_;
376     return if $arg{'NoSignalHandlers'};
377
378 # Signal handlers
379 ## This is the default handling of warnings and die'ings in the code
380 ## (including other used modules - maybe except for errors catched by
381 ## Mason).  It will log all problems through the standard logging
382 ## mechanism (see above).
383
384     $SIG{__WARN__} = sub {
385         # The 'wide character' warnings has to be silenced for now, at least
386         # until HTML::Mason offers a sane way to process both raw output and
387         # unicode strings.
388         # use 'goto &foo' syntax to hide ANON sub from stack
389         if( index($_[0], 'Wide character in ') != 0 ) {
390             unshift @_, $RT::Logger, qw(level warning message);
391             goto &Log::Dispatch::log;
392         }
393         # Return value is used only by RT::Test to filter warnings from
394         # reaching the Test::NoWarnings catcher.  If Log::Dispatch::log() ever
395         # starts returning 'IGNORE', we'll need to switch to something more
396         # clever.  I don't expect that to happen.
397         return 'IGNORE';
398     };
399
400 #When we call die, trap it and log->crit with the value of the die.
401
402     $SIG{__DIE__}  = sub {
403         # if we are not in eval and perl is not parsing code
404         # then rollback transactions and log RT error
405         unless ($^S || !defined $^S ) {
406             $RT::Handle->Rollback(1) if $RT::Handle;
407             $RT::Logger->crit("$_[0]") if $RT::Logger;
408         }
409         die $_[0];
410     };
411 }
412
413
414 sub CheckPerlRequirements {
415     if ($^V < 5.008003) {
416         die sprintf "RT requires Perl v5.8.3 or newer.  Your current Perl is v%vd\n", $^V;
417     }
418
419     # use $error here so the following "die" can still affect the global $@
420     my $error;
421     {
422         local $@;
423         eval {
424             my $x = '';
425             my $y = \$x;
426             require Scalar::Util;
427             Scalar::Util::weaken($y);
428         };
429         $error = $@;
430     }
431
432     if ($error) {
433         die <<"EOF";
434
435 RT requires the Scalar::Util module be built with support for  the 'weaken'
436 function.
437
438 It is sometimes the case that operating system upgrades will replace
439 a working Scalar::Util with a non-working one. If your system was working
440 correctly up until now, this is likely the cause of the problem.
441
442 Please reinstall Scalar::Util, being careful to let it build with your C
443 compiler. Usually this is as simple as running the following command as
444 root.
445
446     perl -MCPAN -e'install Scalar::Util'
447
448 EOF
449
450     }
451 }
452
453 =head2 InitClasses
454
455 Load all modules that define base classes.
456
457 =cut
458
459 sub InitClasses {
460     shift if @_%2; # so we can call it as a function or method
461     my %args = (@_);
462     require RT::Tickets;
463     require RT::Transactions;
464     require RT::Attachments;
465     require RT::Users;
466     require RT::Principals;
467     require RT::CurrentUser;
468     require RT::Templates;
469     require RT::Queues;
470     require RT::ScripActions;
471     require RT::ScripConditions;
472     require RT::Scrips;
473     require RT::Groups;
474     require RT::GroupMembers;
475     require RT::CustomFields;
476     require RT::CustomFieldValues;
477     require RT::ObjectCustomFields;
478     require RT::ObjectCustomFieldValues;
479     require RT::Attributes;
480     require RT::Dashboard;
481     require RT::Approval;
482     require RT::Lifecycle;
483     require RT::Link;
484     require RT::Links;
485     require RT::Article;
486     require RT::Articles;
487     require RT::Class;
488     require RT::Classes;
489     require RT::ObjectClass;
490     require RT::ObjectClasses;
491     require RT::ObjectTopic;
492     require RT::ObjectTopics;
493     require RT::Topic;
494     require RT::Topics;
495
496     # on a cold server (just after restart) people could have an object
497     # in the session, as we deserialize it so we never call constructor
498     # of the class, so the list of accessible fields is empty and we die
499     # with "Method xxx is not implemented in RT::SomeClass"
500
501     # without this, we also can never call _ClassAccessible, because we
502     # won't have filled RT::Record::_TABLE_ATTR
503     $_->_BuildTableAttributes foreach qw(
504         RT::Ticket
505         RT::Transaction
506         RT::Attachment
507         RT::User
508         RT::Principal
509         RT::Template
510         RT::Queue
511         RT::ScripAction
512         RT::ScripCondition
513         RT::Scrip
514         RT::Group
515         RT::GroupMember
516         RT::CustomField
517         RT::CustomFieldValue
518         RT::ObjectCustomField
519         RT::ObjectCustomFieldValue
520         RT::Attribute
521         RT::ACE
522         RT::Link
523         RT::Article
524         RT::Class
525         RT::ObjectClass
526         RT::ObjectTopic
527         RT::Topic
528     );
529
530     if ( $args{'Heavy'} ) {
531         # load scrips' modules
532         my $scrips = RT::Scrips->new(RT->SystemUser);
533         $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' );
534         while ( my $scrip = $scrips->Next ) {
535             local $@;
536             eval { $scrip->LoadModules } or
537                 $RT::Logger->error("Invalid Scrip ".$scrip->Id.".  Unable to load the Action or Condition.  ".
538                                    "You should delete or repair this Scrip in the admin UI.\n$@\n");
539         }
540
541         foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) {
542             local $@;
543             eval "require $class; 1" or $RT::Logger->error(
544                 "Class '$class' is listed in CustomFieldValuesSources option"
545                 ." in the config, but we failed to load it:\n$@\n"
546             );
547         }
548
549     }
550 }
551
552 =head2 InitSystemObjects
553
554 Initializes system objects: C<$RT::System>, C<< RT->SystemUser >>
555 and C<< RT->Nobody >>.
556
557 =cut
558
559 sub InitSystemObjects {
560
561     #RT's system user is a genuine database user. its id lives here
562     require RT::CurrentUser;
563     $SystemUser = RT::CurrentUser->new;
564     $SystemUser->LoadByName('RT_System');
565
566     #RT's "nobody user" is a genuine database user. its ID lives here.
567     $Nobody = RT::CurrentUser->new;
568     $Nobody->LoadByName('Nobody');
569
570     require RT::System;
571     $System = RT::System->new( $SystemUser );
572 }
573
574 =head1 CLASS METHODS
575
576 =head2 Config
577
578 Returns the current L<config object|RT::Config>, but note that
579 you must L<load config|/LoadConfig> first otherwise this method
580 returns undef.
581
582 Method can be called as class method.
583
584 =cut
585
586 sub Config { return $Config || shift->LoadConfig(); }
587
588 =head2 DatabaseHandle
589
590 Returns the current L<database handle object|RT::Handle>.
591
592 See also L</ConnectToDatabase>.
593
594 =cut
595
596 sub DatabaseHandle { return $Handle }
597
598 =head2 Logger
599
600 Returns the logger. See also L</InitLogging>.
601
602 =cut
603
604 sub Logger { return $Logger }
605
606 =head2 System
607
608 Returns the current L<system object|RT::System>. See also
609 L</InitSystemObjects>.
610
611 =cut
612
613 sub System { return $System }
614
615 =head2 SystemUser
616
617 Returns the system user's object, it's object of
618 L<RT::CurrentUser> class that represents the system. See also
619 L</InitSystemObjects>.
620
621 =cut
622
623 sub SystemUser { return $SystemUser }
624
625 =head2 Nobody
626
627 Returns object of Nobody. It's object of L<RT::CurrentUser> class
628 that represents a user who can own ticket and nothing else. See
629 also L</InitSystemObjects>.
630
631 =cut
632
633 sub Nobody { return $Nobody }
634
635 sub PrivilegedUsers {
636     if (!$_Privileged) {
637     $_Privileged = RT::Group->new(RT->SystemUser);
638     $_Privileged->LoadSystemInternalGroup('Privileged');
639     }
640     return $_Privileged;
641 }
642
643 sub UnprivilegedUsers {
644     if (!$_Unprivileged) {
645     $_Unprivileged = RT::Group->new(RT->SystemUser);
646     $_Unprivileged->LoadSystemInternalGroup('Unprivileged');
647     }
648     return $_Unprivileged;
649 }
650
651
652 =head2 Plugins
653
654 Returns a listref of all Plugins currently configured for this RT instance.
655 You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
656
657 =cut
658
659 our @PLUGINS = ();
660 sub Plugins {
661     my $self = shift;
662     unless (@PLUGINS) {
663         $self->InitPluginPaths;
664         @PLUGINS = $self->InitPlugins;
665     }
666     return \@PLUGINS;
667 }
668
669 =head2 PluginDirs
670
671 Takes an optional subdir (e.g. po, lib, etc.) and returns a list of
672 directories from plugins where that subdirectory exists.
673
674 This code does not check plugin names, plugin validitity, or load
675 plugins (see L</InitPlugins>) in any way, and requires that RT's
676 configuration have been already loaded.
677
678 =cut
679
680 sub PluginDirs {
681     my $self = shift;
682     my $subdir = shift;
683
684     require RT::Plugin;
685
686     my @res;
687     foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
688         my $path = RT::Plugin->new( name => $plugin )->Path( $subdir );
689         next unless -d $path;
690         push @res, $path;
691     }
692     return @res;
693 }
694
695 =head2 InitPluginPaths
696
697 Push plugins' lib paths into @INC right after F<local/lib>.
698 In case F<local/lib> isn't in @INC, append them to @INC
699
700 =cut
701
702 sub InitPluginPaths {
703     my $self = shift || __PACKAGE__;
704
705     my @lib_dirs = $self->PluginDirs('lib');
706
707     my @tmp_inc;
708     my $added;
709     for (@INC) {
710         if ( Cwd::realpath($_) eq $RT::LocalLibPath) {
711             push @tmp_inc, $_, @lib_dirs;
712             $added = 1;
713         } else {
714             push @tmp_inc, $_;
715         }
716     }
717
718     # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
719     push @tmp_inc, @lib_dirs unless $added;
720
721     my %seen;
722     @INC = grep !$seen{$_}++, @tmp_inc;
723 }
724
725 =head2 InitPlugins
726
727 Initialize all Plugins found in the RT configuration file, setting up
728 their lib and L<HTML::Mason> component roots.
729
730 =cut
731
732 sub InitPlugins {
733     my $self    = shift;
734     my @plugins;
735     require RT::Plugin;
736     foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
737         $plugin->require;
738         die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
739         push @plugins, RT::Plugin->new(name =>$plugin);
740     }
741     return @plugins;
742 }
743
744
745 sub InstallMode {
746     my $self = shift;
747     if (@_) {
748         my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
749         if ($_[0] and $integrity) {
750             # Trying to turn install mode on but we have a good DB!
751             require Carp;
752             $RT::Logger->error(
753                 Carp::longmess("Something tried to turn on InstallMode but we have DB integrity!")
754             );
755         }
756         else {
757             $_INSTALL_MODE = shift;
758             if($_INSTALL_MODE) {
759                 require RT::CurrentUser;
760                $SystemUser = RT::CurrentUser->new();
761             }
762         }
763     }
764     return $_INSTALL_MODE;
765 }
766
767 sub LoadGeneratedData {
768     my $class = shift;
769     my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
770
771     require "$pm_path/RT/Generated.pm" || die "Couldn't load RT::Generated: $@";
772     $class->CanonicalizeGeneratedPaths();
773 }
774
775 sub CanonicalizeGeneratedPaths {
776     my $class = shift;
777     unless ( File::Spec->file_name_is_absolute($EtcPath) ) {
778
779    # if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}.
780    # otherwise RT.pm will make the source dir(where we configure RT) be the
781    # BasePath instead of the one specified by --prefix
782         unless ( -d $BasePath
783                  && File::Spec->file_name_is_absolute($BasePath) )
784         {
785             my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
786
787      # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'}
788      # is not always absolute
789             $BasePath = File::Spec->rel2abs(
790                           File::Spec->catdir( $pm_path, File::Spec->updir ) );
791         }
792
793         $BasePath = Cwd::realpath($BasePath);
794
795         for my $path (
796                     qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
797                     LocalLibPath LexiconPath LocalLexiconPath PluginPath
798                     LocalPluginPath MasonComponentRoot MasonLocalComponentRoot
799                     MasonDataDir MasonSessionDir/
800                      )
801         {
802             no strict 'refs';
803
804             # just change relative ones
805             $$path = File::Spec->catfile( $BasePath, $$path )
806                 unless File::Spec->file_name_is_absolute($$path);
807         }
808     }
809
810 }
811
812 =head2 AddJavaScript
813
814 helper method to add js files to C<JSFiles> config.
815 to add extra js files, you can add the following line
816 in the plugin's main file:
817
818     RT->AddJavaScript( 'foo.js', 'bar.js' ); 
819
820 =cut
821
822 sub AddJavaScript {
823     my $self = shift;
824
825     my @old = RT->Config->Get('JSFiles');
826     RT->Config->Set( 'JSFiles', @old, @_ );
827     return RT->Config->Get('JSFiles');
828 }
829
830 =head2 AddStyleSheets
831
832 helper method to add css files to C<CSSFiles> config
833
834 to add extra css files, you can add the following line
835 in the plugin's main file:
836
837     RT->AddStyleSheets( 'foo.css', 'bar.css' ); 
838
839 =cut
840
841 sub AddStyleSheets {
842     my $self = shift;
843     my @old = RT->Config->Get('CSSFiles');
844     RT->Config->Set( 'CSSFiles', @old, @_ );
845     return RT->Config->Get('CSSFiles');
846 }
847
848 =head2 JavaScript
849
850 helper method of RT->Config->Get('JSFiles')
851
852 =cut
853
854 sub JavaScript {
855     return RT->Config->Get('JSFiles');
856 }
857
858 =head2 StyleSheets
859
860 helper method of RT->Config->Get('CSSFiles')
861
862 =cut
863
864 sub StyleSheets {
865     return RT->Config->Get('CSSFiles');
866 }
867
868 =head1 BUGS
869
870 Please report them to rt-bugs@bestpractical.com, if you know what's
871 broken and have at least some idea of what needs to be fixed.
872
873 If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
874
875 =head1 SEE ALSO
876
877 L<RT::StyleGuide>
878 L<DBIx::SearchBuilder>
879
880 =cut
881
882 require RT::Base;
883 RT::Base->_ImportOverlays();
884
885 1;