Upgrade to 4.0.13
[usit-rt.git] / lib / RT.pm
CommitLineData
84fb5b46
MKG
1# BEGIN BPS TAGGED BLOCK {{{
2#
3# COPYRIGHT:
4#
403d7b0b 5# This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
84fb5b46
MKG
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
49use strict;
50use warnings;
51
52package RT;
53
54
55use File::Spec ();
56use Cwd ();
57
58use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_Privileged $_Unprivileged $_INSTALL_MODE);
59
60use 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
78RT->LoadGeneratedData();
79
80=head1 NAME
81
82RT - Request Tracker
83
84=head1 SYNOPSIS
85
5b0d0914
MKG
86A fully featured request tracker package.
87
88This documentation describes the point-of-entry for RT's Perl API. To learn
89more about what RT is and what it can do for you, visit
90L<https://bestpractical.com/rt>.
84fb5b46
MKG
91
92=head1 DESCRIPTION
93
94=head2 INITIALIZATION
95
5b0d0914
MKG
96If you're using RT's Perl libraries, you need to initialize RT before using any
97of the modules.
98
99You have the option of handling the timing of config loading and the actual
100init sequence yourself with:
101
102 use RT;
103 BEGIN {
104 RT->LoadConfig;
105 RT->Init;
106 }
107
108or you can let RT do it all:
109
110 use RT -init;
111
112This second method is particular useful when writing one-liners to interact with RT:
113
114 perl -MRT=-init -e '...'
115
116The first method is necessary if you need to delay or conditionalize
117initialization or if you want to fiddle with C<< RT->Config >> between loading
118the 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
84fb5b46
MKG
136=head2 LoadConfig
137
138Load RT's config file. First, the site configuration file
139(F<RT_SiteConfig.pm>) is loaded, in order to establish overall site
140settings like hostname and name of RT instance. Then, the core
141configuration file (F<RT_Config.pm>) is loaded to set fallback values
142for all settings; it bases some values on settings from the site
143configuration file.
144
145In order for the core configuration to not override the site's
146settings, the function C<Set> is used; it only sets values if they
147have not been set already.
148
149=cut
150
151sub 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
177L<Connects to the database|/ConnectToDatabase>, L<initilizes system
178objects|/InitSystemObjects>, L<preloads classes|/InitClasses>, L<sets
179up logging|/InitLogging>, and L<loads plugins|/InitPlugins>.
180
181=cut
182
183sub 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
202Get a database connection. See also L</Handle>.
203
204=cut
205
206sub ConnectToDatabase {
207 require RT::Handle;
208 $Handle = RT::Handle->new unless $Handle;
209 $Handle->Connect;
210 return $Handle;
211}
212
213=head2 InitLogging
214
215Create the Logger object and set up signal handlers.
216
217=cut
218
219sub 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
403d7b0b
MKG
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
84fb5b46
MKG
369sub 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 }
403d7b0b
MKG
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';
84fb5b46
MKG
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
407sub 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
428RT requires the Scalar::Util module be built with support for the 'weaken'
429function.
430
431It is sometimes the case that operating system upgrades will replace
432a working Scalar::Util with a non-working one. If your system was working
433correctly up until now, this is likely the cause of the problem.
434
435Please reinstall Scalar::Util, being careful to let it build with your C
436compiler. Usually this is as simple as running the following command as
437root.
438
439 perl -MCPAN -e'install Scalar::Util'
440
441EOF
442
443 }
444}
445
446=head2 InitClasses
447
448Load all modules that define base classes.
449
450=cut
451
452sub 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;
b5747ff2 477 require RT::Links;
84fb5b46
MKG
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
547Initializes system objects: C<$RT::System>, C<< RT->SystemUser >>
548and C<< RT->Nobody >>.
549
550=cut
551
552sub 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
571Returns the current L<config object|RT::Config>, but note that
572you must L<load config|/LoadConfig> first otherwise this method
573returns undef.
574
575Method can be called as class method.
576
577=cut
578
579sub Config { return $Config || shift->LoadConfig(); }
580
581=head2 DatabaseHandle
582
583Returns the current L<database handle object|RT::Handle>.
584
585See also L</ConnectToDatabase>.
586
587=cut
588
589sub DatabaseHandle { return $Handle }
590
591=head2 Logger
592
593Returns the logger. See also L</InitLogging>.
594
595=cut
596
597sub Logger { return $Logger }
598
599=head2 System
600
601Returns the current L<system object|RT::System>. See also
602L</InitSystemObjects>.
603
604=cut
605
606sub System { return $System }
607
608=head2 SystemUser
609
610Returns the system user's object, it's object of
611L<RT::CurrentUser> class that represents the system. See also
612L</InitSystemObjects>.
613
614=cut
615
616sub SystemUser { return $SystemUser }
617
618=head2 Nobody
619
620Returns object of Nobody. It's object of L<RT::CurrentUser> class
621that represents a user who can own ticket and nothing else. See
622also L</InitSystemObjects>.
623
624=cut
625
626sub Nobody { return $Nobody }
627
628sub PrivilegedUsers {
629 if (!$_Privileged) {
630 $_Privileged = RT::Group->new(RT->SystemUser);
631 $_Privileged->LoadSystemInternalGroup('Privileged');
632 }
633 return $_Privileged;
634}
635
636sub 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
647Returns a listref of all Plugins currently configured for this RT instance.
648You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
649
650=cut
651
652our @PLUGINS = ();
653sub 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
664Takes an optional subdir (e.g. po, lib, etc.) and returns a list of
665directories from plugins where that subdirectory exists.
666
667This code does not check plugin names, plugin validitity, or load
668plugins (see L</InitPlugins>) in any way, and requires that RT's
669configuration have been already loaded.
670
671=cut
672
673sub 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
690Push plugins' lib paths into @INC right after F<local/lib>.
691In case F<local/lib> isn't in @INC, append them to @INC
692
693=cut
694
695sub 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
720Initialize all Plugins found in the RT configuration file, setting up
721their lib and L<HTML::Mason> component roots.
722
723=cut
724
725sub 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
738sub 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
760sub 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
768sub 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
807helper method to add js files to C<JSFiles> config.
b5747ff2 808to add extra js files, you can add the following line
84fb5b46
MKG
809in the plugin's main file:
810
811 RT->AddJavaScript( 'foo.js', 'bar.js' );
812
813=cut
814
815sub 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
825helper method to add css files to C<CSSFiles> config
826
827to add extra css files, you can add the following line
828in the plugin's main file:
829
830 RT->AddStyleSheets( 'foo.css', 'bar.css' );
831
832=cut
833
834sub 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
843helper method of RT->Config->Get('JSFiles')
844
845=cut
846
847sub JavaScript {
848 return RT->Config->Get('JSFiles');
849}
850
851=head2 StyleSheets
852
853helper method of RT->Config->Get('CSSFiles')
854
855=cut
856
857sub StyleSheets {
858 return RT->Config->Get('CSSFiles');
859}
860
861=head1 BUGS
862
863Please report them to rt-bugs@bestpractical.com, if you know what's
864broken and have at least some idea of what needs to be fixed.
865
866If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
867
868=head1 SEE ALSO
869
870L<RT::StyleGuide>
871L<DBIx::SearchBuilder>
872
873=cut
874
875require RT::Base;
876RT::Base->_ImportOverlays();
877
8781;