1 # BEGIN BPS TAGGED BLOCK {{{
5 # This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
6 # <sales@bestpractical.com>
8 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
30 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
47 # END BPS TAGGED BLOCK }}}
59 use Scalar::Util qw(blessed);
60 use UNIVERSAL::require;
62 use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_Privileged $_Unprivileged $_INSTALL_MODE);
79 $MasonLocalComponentRoot
84 RT->LoadGeneratedData();
92 A fully featured request tracker package.
94 This documentation describes the point-of-entry for RT's Perl API. To learn
95 more about what RT is and what it can do for you, visit
96 L<https://bestpractical.com/rt>.
100 =head2 INITIALIZATION
102 If you're using RT's Perl libraries, you need to initialize RT before using any
105 You have the option of handling the timing of config loading and the actual
106 init sequence yourself with:
114 or you can let RT do it all:
118 This second method is particular useful when writing one-liners to interact with RT:
120 perl -MRT=-init -e '...'
122 The first method is necessary if you need to delay or conditionalize
123 initialization or if you want to fiddle with C<< RT->Config >> between loading
124 the config files and initializing the RT environment.
132 my $action = shift || '';
134 if ($action eq "-init" and not $DID_IMPORT_INIT) {
137 $DID_IMPORT_INIT = 1;
144 Load RT's config file. First, the site configuration file
145 (F<RT_SiteConfig.pm>) is loaded, in order to establish overall site
146 settings like hostname and name of RT instance. Then, the core
147 configuration file (F<RT_Config.pm>) is loaded to set fallback values
148 for all settings; it bases some values on settings from the site
151 In order for the core configuration to not override the site's
152 settings, the function C<Set> is used; it only sets values if they
153 have not been set already.
159 $Config = RT::Config->new;
160 $Config->LoadConfigs;
163 # RT::Essentials mistakenly recommends that WebPath be set to '/'.
164 # If the user does that, do what they mean.
165 $RT::WebPath = '' if ($RT::WebPath eq '/');
167 # Fix relative LogDir; It cannot be fixed in a PostLoadCheck, as
168 # they are run after logging is enabled.
169 unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) {
170 $Config->Set( LogDir =>
171 File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) );
179 L<Connects to the database|/ConnectToDatabase>, L<initilizes system
180 objects|/InitSystemObjects>, L<preloads classes|/InitClasses>, L<sets
181 up logging|/InitLogging>, and L<loads plugins|/InitPlugins>.
186 shift if @_%2; # code is inconsistent about calling as method
189 CheckPerlRequirements();
193 #Get a database connection
199 _BuildTableAttributes();
201 RT->Config->PostLoadCheck;
202 RT::Lifecycle->new->FillCache;
205 =head2 ConnectToDatabase
207 Get a database connection. See also L</Handle>.
211 sub ConnectToDatabase {
213 $Handle = RT::Handle->new unless $Handle;
220 Create the Logger object and set up signal handlers.
226 # We have to set the record separator ($, man perlvar)
227 # or Log::Dispatch starts getting
228 # really pissy, as some other module we use unsets it.
230 use Log::Dispatch 1.6;
233 map( { $_ => } 0..7 ),
238 error => 4, 'err' => 4,
239 critical => 5, crit => 5,
241 emergency => 7, emerg => 7,
244 unless ( $RT::Logger ) {
246 $RT::Logger = Log::Dispatch->new;
248 my $stack_from_level;
249 if ( $stack_from_level = RT->Config->Get('LogStackTraces') ) {
250 # if option has old style '\d'(true) value
251 $stack_from_level = 0 if $stack_from_level =~ /^\d+$/;
252 $stack_from_level = $level_to_num{ $stack_from_level } || 0;
254 $stack_from_level = 99; # don't log
257 my $simple_cb = sub {
258 # if this code throw any warning we can get segfault
262 # skip Log::* stack frames
264 $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
265 my ($package, $filename, $line) = caller($frame);
267 # Encode to bytes, so we don't send wide characters
268 $p{message} = Encode::encode("UTF-8", $p{message});
270 $p{'message'} =~ s/(?:\r*\n)+$//;
271 return "[$$] [". gmtime(time) ."] [". $p{'level'} ."]: "
272 . $p{'message'} ." ($filename:$line)\n";
275 my $syslog_cb = sub {
276 # if this code throw any warning we can get segfault
280 my $frame = 0; # stack frame index
281 # skip Log::* stack frames
282 $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
283 my ($package, $filename, $line) = caller($frame);
285 # Encode to bytes, so we don't send wide characters
286 $p{message} = Encode::encode("UTF-8", $p{message});
288 $p{message} =~ s/(?:\r*\n)+$//;
289 if ($p{level} eq 'debug') {
290 return "[$$] $p{message} ($filename:$line)\n";
292 return "[$$] $p{message}\n";
299 return $p{'message'} unless $level_to_num{ $p{'level'} } >= $stack_from_level;
301 require Devel::StackTrace;
302 my $trace = Devel::StackTrace->new( ignore_class => [ 'Log::Dispatch', 'Log::Dispatch::Base' ] );
303 return $p{'message'} . $trace->as_string;
305 # skip calling of the Log::* subroutins
307 $frame++ while caller($frame) && caller($frame) =~ /^Log::/;
308 $frame++ while caller($frame) && (caller($frame))[3] =~ /^Log::/;
310 $p{'message'} .= "\nStack trace:\n";
311 while( my ($package, $filename, $line, $sub) = caller($frame++) ) {
312 $p{'message'} .= "\t$sub(...) called at $filename:$line\n";
314 return $p{'message'};
317 if ( $Config->Get('LogToFile') ) {
318 my ($filename, $logdir) = (
319 $Config->Get('LogToFileNamed') || 'rt.log',
320 $Config->Get('LogDir') || File::Spec->catdir( $VarPath, 'log' ),
322 if ( $filename =~ m![/\\]! ) { # looks like an absolute path.
323 ($logdir) = $filename =~ m{^(.*[/\\])};
326 $filename = File::Spec->catfile( $logdir, $filename );
329 unless ( -d $logdir && ( ( -f $filename && -w $filename ) || -w $logdir ) ) {
330 # localizing here would be hard when we don't have a current user yet
331 die "Log file '$filename' couldn't be written or created.\n RT can't run.";
334 require Log::Dispatch::File;
335 $RT::Logger->add( Log::Dispatch::File->new
337 min_level=> $Config->Get('LogToFile'),
338 filename=> $filename,
340 callbacks => [ $simple_cb, $stack_cb ],
343 if ( $Config->Get('LogToSTDERR') ) {
344 require Log::Dispatch::Screen;
345 $RT::Logger->add( Log::Dispatch::Screen->new
347 min_level => $Config->Get('LogToSTDERR'),
348 callbacks => [ $simple_cb, $stack_cb ],
352 if ( $Config->Get('LogToSyslog') ) {
353 require Log::Dispatch::Syslog;
354 $RT::Logger->add(Log::Dispatch::Syslog->new
357 min_level => $Config->Get('LogToSyslog'),
358 callbacks => [ $syslog_cb, $stack_cb ],
360 $Config->Get('LogToSyslogConf'),
364 InitSignalHandlers();
367 sub InitSignalHandlers {
370 ## This is the default handling of warnings and die'ings in the code
371 ## (including other used modules - maybe except for errors catched by
372 ## Mason). It will log all problems through the standard logging
373 ## mechanism (see above).
375 $SIG{__WARN__} = sub {
376 # use 'goto &foo' syntax to hide ANON sub from stack
377 unshift @_, $RT::Logger, qw(level warning message);
378 goto &Log::Dispatch::log;
381 #When we call die, trap it and log->crit with the value of the die.
383 $SIG{__DIE__} = sub {
384 # if we are not in eval and perl is not parsing code
385 # then rollback transactions and log RT error
386 unless ($^S || !defined $^S ) {
387 $RT::Handle->Rollback(1) if $RT::Handle;
388 $RT::Logger->crit("$_[0]") if $RT::Logger;
395 sub CheckPerlRequirements {
396 eval {require 5.010_001};
398 die sprintf "RT requires Perl v5.10.1 or newer. Your current Perl is v%vd\n", $^V;
401 # use $error here so the following "die" can still affect the global $@
408 require Scalar::Util;
409 Scalar::Util::weaken($y);
417 RT requires the Scalar::Util module be built with support for the 'weaken'
420 It is sometimes the case that operating system upgrades will replace
421 a working Scalar::Util with a non-working one. If your system was working
422 correctly up until now, this is likely the cause of the problem.
424 Please reinstall Scalar::Util, being careful to let it build with your C
425 compiler. Usually this is as simple as running the following command as
428 perl -MCPAN -e'install Scalar::Util'
437 Load all modules that define base classes.
442 shift if @_%2; # so we can call it as a function or method
445 require RT::Transactions;
446 require RT::Attachments;
448 require RT::Principals;
449 require RT::CurrentUser;
450 require RT::Templates;
452 require RT::ScripActions;
453 require RT::ScripConditions;
456 require RT::GroupMembers;
457 require RT::CustomFields;
458 require RT::CustomFieldValues;
459 require RT::ObjectCustomFields;
460 require RT::ObjectCustomFieldValues;
461 require RT::Attributes;
462 require RT::Dashboard;
463 require RT::Approval;
464 require RT::Lifecycle;
468 require RT::Articles;
471 require RT::ObjectClass;
472 require RT::ObjectClasses;
473 require RT::ObjectTopic;
474 require RT::ObjectTopics;
480 _BuildTableAttributes();
482 if ( $args{'Heavy'} ) {
483 # load scrips' modules
484 my $scrips = RT::Scrips->new(RT->SystemUser);
485 while ( my $scrip = $scrips->Next ) {
487 eval { $scrip->LoadModules } or
488 $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ".
489 "You should delete or repair this Scrip in the admin UI.\n$@\n");
492 foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) {
493 $class->require or $RT::Logger->error(
494 "Class '$class' is listed in CustomFieldValuesSources option"
495 ." in the config, but we failed to load it:\n$@\n"
502 sub _BuildTableAttributes {
503 # on a cold server (just after restart) people could have an object
504 # in the session, as we deserialize it so we never call constructor
505 # of the class, so the list of accessible fields is empty and we die
506 # with "Method xxx is not implemented in RT::SomeClass"
508 # without this, we also can never call _ClassAccessible, because we
509 # won't have filled RT::Record::_TABLE_ATTR
510 $_->_BuildTableAttributes foreach qw(
526 RT::ObjectCustomField
527 RT::ObjectCustomFieldValue
539 =head2 InitSystemObjects
541 Initializes system objects: C<$RT::System>, C<< RT->SystemUser >>
542 and C<< RT->Nobody >>.
546 sub InitSystemObjects {
548 #RT's system user is a genuine database user. its id lives here
549 require RT::CurrentUser;
550 $SystemUser = RT::CurrentUser->new;
551 $SystemUser->LoadByName('RT_System');
553 #RT's "nobody user" is a genuine database user. its ID lives here.
554 $Nobody = RT::CurrentUser->new;
555 $Nobody->LoadByName('Nobody');
558 $System = RT::System->new( $SystemUser );
565 Returns the current L<config object|RT::Config>, but note that
566 you must L<load config|/LoadConfig> first otherwise this method
569 Method can be called as class method.
573 sub Config { return $Config || shift->LoadConfig(); }
575 =head2 DatabaseHandle
577 Returns the current L<database handle object|RT::Handle>.
579 See also L</ConnectToDatabase>.
583 sub DatabaseHandle { return $Handle }
587 Returns the logger. See also L</InitLogging>.
591 sub Logger { return $Logger }
595 Returns the current L<system object|RT::System>. See also
596 L</InitSystemObjects>.
600 sub System { return $System }
604 Returns the system user's object, it's object of
605 L<RT::CurrentUser> class that represents the system. See also
606 L</InitSystemObjects>.
610 sub SystemUser { return $SystemUser }
614 Returns object of Nobody. It's object of L<RT::CurrentUser> class
615 that represents a user who can own ticket and nothing else. See
616 also L</InitSystemObjects>.
620 sub Nobody { return $Nobody }
622 sub PrivilegedUsers {
624 $_Privileged = RT::Group->new(RT->SystemUser);
625 $_Privileged->LoadSystemInternalGroup('Privileged');
630 sub UnprivilegedUsers {
631 if (!$_Unprivileged) {
632 $_Unprivileged = RT::Group->new(RT->SystemUser);
633 $_Unprivileged->LoadSystemInternalGroup('Unprivileged');
635 return $_Unprivileged;
641 Returns a listref of all Plugins currently configured for this RT instance.
642 You can define plugins by adding them to the @Plugins list in your RT_SiteConfig
652 $self->InitPluginPaths;
653 @PLUGINS = $self->InitPlugins;
661 Takes an optional subdir (e.g. po, lib, etc.) and returns a list of
662 directories from plugins where that subdirectory exists.
664 This code does not check plugin names, plugin validitity, or load
665 plugins (see L</InitPlugins>) in any way, and requires that RT's
666 configuration have been already loaded.
677 foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
678 my $path = RT::Plugin->new( name => $plugin )->Path( $subdir );
679 next unless -d $path;
685 =head2 InitPluginPaths
687 Push plugins' lib paths into @INC right after F<local/lib>.
688 In case F<local/lib> isn't in @INC, append them to @INC
692 sub InitPluginPaths {
693 my $self = shift || __PACKAGE__;
695 my @lib_dirs = $self->PluginDirs('lib');
700 my $realpath = Cwd::realpath($_);
701 next unless defined $realpath;
702 if ( $realpath eq $RT::LocalLibPath) {
703 push @tmp_inc, $_, @lib_dirs;
710 # append @lib_dirs in case $RT::LocalLibPath isn't in @INC
711 push @tmp_inc, @lib_dirs unless $added;
714 @INC = grep !$seen{$_}++, @tmp_inc;
719 Initialize all Plugins found in the RT configuration file, setting up
720 their lib and L<HTML::Mason> component roots.
728 foreach my $plugin (grep $_, RT->Config->Get('Plugins')) {
730 die $UNIVERSAL::require::ERROR if ($UNIVERSAL::require::ERROR);
731 push @plugins, RT::Plugin->new(name =>$plugin);
740 my ($integrity, $state, $msg) = RT::Handle->CheckIntegrity;
741 if ($_[0] and $integrity) {
742 # Trying to turn install mode on but we have a good DB!
745 Carp::longmess("Something tried to turn on InstallMode but we have DB integrity!")
749 $_INSTALL_MODE = shift;
751 require RT::CurrentUser;
752 $SystemUser = RT::CurrentUser->new();
756 return $_INSTALL_MODE;
759 sub LoadGeneratedData {
761 my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
763 require "$pm_path/RT/Generated.pm" || die "Couldn't load RT::Generated: $@";
764 $class->CanonicalizeGeneratedPaths();
767 sub CanonicalizeGeneratedPaths {
769 unless ( File::Spec->file_name_is_absolute($EtcPath) ) {
771 # if BasePath exists and is absolute, we won't infer it from $INC{'RT.pm'}.
772 # otherwise RT.pm will make the source dir(where we configure RT) be the
773 # BasePath instead of the one specified by --prefix
774 unless ( -d $BasePath
775 && File::Spec->file_name_is_absolute($BasePath) )
777 my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
779 # need rel2abs here is to make sure path is absolute, since $INC{'RT.pm'}
780 # is not always absolute
781 $BasePath = File::Spec->rel2abs(
782 File::Spec->catdir( $pm_path, File::Spec->updir ) );
785 $BasePath = Cwd::realpath($BasePath);
788 qw/EtcPath BinPath SbinPath VarPath LocalPath StaticPath LocalEtcPath
789 LocalLibPath LexiconPath LocalLexiconPath PluginPath
790 LocalPluginPath LocalStaticPath MasonComponentRoot MasonLocalComponentRoot
791 MasonDataDir MasonSessionDir/
796 # just change relative ones
797 $$path = File::Spec->catfile( $BasePath, $$path )
798 unless File::Spec->file_name_is_absolute($$path);
806 Helper method to add JS files to the C<@JSFiles> config at runtime.
808 To add files, you can add the following line to your extension's main C<.pm>
811 RT->AddJavaScript( 'foo.js', 'bar.js' );
813 Files are expected to be in a static root in a F<js/> directory, such as
814 F<static/js/> in your extension or F<local/static/js/> for local overlays.
821 my @old = RT->Config->Get('JSFiles');
822 RT->Config->Set( 'JSFiles', @old, @_ );
823 return RT->Config->Get('JSFiles');
826 =head2 AddStyleSheets
828 Helper method to add CSS files to the C<@CSSFiles> config at runtime.
830 To add files, you can add the following line to your extension's main C<.pm>
833 RT->AddStyleSheets( 'foo.css', 'bar.css' );
835 Files are expected to be in a static root in a F<css/> directory, such as
836 F<static/css/> in your extension or F<local/static/css/> for local
843 my @old = RT->Config->Get('CSSFiles');
844 RT->Config->Set( 'CSSFiles', @old, @_ );
845 return RT->Config->Get('CSSFiles');
850 helper method of RT->Config->Get('JSFiles')
855 return RT->Config->Get('JSFiles');
860 helper method of RT->Config->Get('CSSFiles')
865 return RT->Config->Get('CSSFiles');
870 Notes that a particular call path is deprecated, and will be removed in
871 a particular release. Puts a warning in the logs indicating such, along
874 Optional arguments include:
880 The release which is slated to remove the method or component
884 A suggestion of what to use in place of the deprecated API
888 Used if not the entire method is being removed, merely a manner of
889 calling it; names the arguments which are deprecated.
893 Overrides the auto-built phrasing of C<Calling function ____ is
894 deprecated> with a custom message.
898 An L<RT::Record> object to print the class and numeric id of. Useful if the
899 admin will need to hunt down a particular object to fix the deprecation
918 my ($function) = (caller(1))[3];
920 if ($function eq "HTML::Mason::Commands::__ANON__") {
921 eval { HTML::Mason::Exception->throw() };
923 my $info = $error->analyze_error;
924 $function = "Mason component ".$info->{frames}[0]->filename;
925 $stack = join("\n", map { sprintf("\t[%s:%d]", $_->filename, $_->line) } @{$info->{frames}});
927 $function = "function $function";
928 $stack = Carp::longmess();
930 $stack =~ s/^.*?\n//; # Strip off call to ->Deprecated
933 if ($args{Message}) {
934 $msg = $args{Message};
935 } elsif ($args{Arguments}) {
936 $msg = "Calling $function with $args{Arguments} is deprecated";
938 $msg = "The $function is deprecated";
940 $msg .= ", and will be removed in RT $args{Remove}"
944 $msg .= " You should use $args{Instead} instead."
947 $msg .= sprintf " Object: %s #%d.", blessed($args{Object}), $args{Object}->id
950 $msg .= " Call stack:\n$stack" if $args{Stack};
952 my $loglevel = $args{LogLevel};
953 RT->Logger->$loglevel($msg);
958 Please report them to rt-bugs@bestpractical.com, if you know what's
959 broken and have at least some idea of what needs to be fixed.
961 If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
966 L<DBIx::SearchBuilder>
971 RT::Base->_ImportOverlays();