#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
+# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
# <sales@bestpractical.com>
#
# (Except where explicitly superseded by other copyright notices)
use strict;
use warnings;
+use 5.010;
package RT;
use Encode ();
use File::Spec ();
use Cwd ();
+use Scalar::Util qw(blessed);
+use UNIVERSAL::require;
use vars qw($Config $System $SystemUser $Nobody $Handle $Logger $_Privileged $_Unprivileged $_INSTALL_MODE);
$BinPath
$SbinPath
$VarPath
+ $FontPath
$LexiconPath
+ $StaticPath
$PluginPath
$LocalPath
$LocalEtcPath
$LocalLibPath
$LocalLexiconPath
+ $LocalStaticPath
$LocalPluginPath
$MasonComponentRoot
$MasonLocalComponentRoot
$MasonDataDir
$MasonSessionDir);
+# Set Email::Address module var before anything else loads.
+# This avoids an algorithmic complexity denial of service vulnerability.
+# See T#157608 and CVE-2015-7686 for more information.
+$Email::Address::COMMENT_NEST_LEVEL = 1;
RT->LoadGeneratedData();
# If the user does that, do what they mean.
$RT::WebPath = '' if ($RT::WebPath eq '/');
- # fix relative LogDir and GnuPG homedir
+ # Fix relative LogDir; It cannot be fixed in a PostLoadCheck, as
+ # they are run after logging is enabled.
unless ( File::Spec->file_name_is_absolute( $Config->Get('LogDir') ) ) {
$Config->Set( LogDir =>
File::Spec->catfile( $BasePath, $Config->Get('LogDir') ) );
}
- my $gpgopts = $Config->Get('GnuPGOptions');
- unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
- $gpgopts->{homedir} = File::Spec->catfile( $BasePath, $gpgopts->{homedir} );
- }
-
return $Config;
}
InitClasses(%args);
InitLogging(%args);
InitPlugins();
+ _BuildTableAttributes();
RT::I18N->Init;
RT->Config->PostLoadCheck;
-
+ RT::Lifecycle->FillCache;
}
=head2 ConnectToDatabase
callbacks => [ $simple_cb, $stack_cb ],
));
}
- if ( $Config->Get('LogToScreen') ) {
+ if ( $Config->Get('LogToSTDERR') ) {
require Log::Dispatch::Screen;
$RT::Logger->add( Log::Dispatch::Screen->new
( name => 'screen',
- min_level => $Config->Get('LogToScreen'),
+ min_level => $Config->Get('LogToSTDERR'),
callbacks => [ $simple_cb, $stack_cb ],
stderr => 1,
));
InitSignalHandlers(%arg);
}
-{ # Work around bug in Log::Dispatch < 2.30, wherein the short forms
- # of ->warn, ->err, and ->crit do not usefully propagate out, unlike
- # ->warning, ->error, and ->critical
- package Log::Dispatch;
- no warnings 'redefine';
- sub warn { shift->warning(@_) }
- sub err { shift->error(@_) }
- sub crit { shift->critical(@_) }
-}
-
sub InitSignalHandlers {
my %arg = @_;
sub CheckPerlRequirements {
- if ($^V < 5.008003) {
- die sprintf "RT requires Perl v5.8.3 or newer. Your current Perl is v%vd\n", $^V;
+ eval {require 5.010_001};
+ if ($@) {
+ die sprintf "RT requires Perl v5.10.1 or newer. Your current Perl is v%vd\n", $^V;
}
# use $error here so the following "die" can still affect the global $@
require RT::ObjectTopics;
require RT::Topic;
require RT::Topics;
+ require RT::Link;
+ require RT::Links;
+
+ _BuildTableAttributes();
+
+ if ( $args{'Heavy'} ) {
+ # load scrips' modules
+ my $scrips = RT::Scrips->new(RT->SystemUser);
+ while ( my $scrip = $scrips->Next ) {
+ local $@;
+ eval { $scrip->LoadModules } or
+ $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ".
+ "You should delete or repair this Scrip in the admin UI.\n$@\n");
+ }
+ foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) {
+ $class->require or $RT::Logger->error(
+ "Class '$class' is listed in CustomFieldValuesSources option"
+ ." in the config, but we failed to load it:\n$@\n"
+ );
+ }
+
+ }
+}
+
+sub _BuildTableAttributes {
# on a cold server (just after restart) people could have an object
# in the session, as we deserialize it so we never call constructor
# of the class, so the list of accessible fields is empty and we die
RT::ScripAction
RT::ScripCondition
RT::Scrip
+ RT::ObjectScrip
RT::Group
RT::GroupMember
RT::CustomField
RT::ObjectCustomFieldValue
RT::Attribute
RT::ACE
- RT::Link
RT::Article
RT::Class
+ RT::Link
RT::ObjectClass
RT::ObjectTopic
RT::Topic
);
-
- if ( $args{'Heavy'} ) {
- # load scrips' modules
- my $scrips = RT::Scrips->new(RT->SystemUser);
- $scrips->Limit( FIELD => 'Stage', OPERATOR => '!=', VALUE => 'Disabled' );
- while ( my $scrip = $scrips->Next ) {
- local $@;
- eval { $scrip->LoadModules } or
- $RT::Logger->error("Invalid Scrip ".$scrip->Id.". Unable to load the Action or Condition. ".
- "You should delete or repair this Scrip in the admin UI.\n$@\n");
- }
-
- foreach my $class ( grep $_, RT->Config->Get('CustomFieldValuesSources') ) {
- local $@;
- eval "require $class; 1" or $RT::Logger->error(
- "Class '$class' is listed in CustomFieldValuesSources option"
- ." in the config, but we failed to load it:\n$@\n"
- );
- }
-
- }
}
=head2 InitSystemObjects
=cut
-our @PLUGINS = ();
sub Plugins {
+ state @PLUGINS;
+ state $DID_INIT = 0;
+
my $self = shift;
- unless (@PLUGINS) {
+ unless ($DID_INIT) {
$self->InitPluginPaths;
@PLUGINS = $self->InitPlugins;
+ $DID_INIT++;
}
- return \@PLUGINS;
+ return [@PLUGINS];
}
=head2 PluginDirs
sub LoadGeneratedData {
my $class = shift;
my $pm_path = ( File::Spec->splitpath( $INC{'RT.pm'} ) )[1];
+ $pm_path = File::Spec->rel2abs( $pm_path );
require "$pm_path/RT/Generated.pm" || die "Couldn't load RT::Generated: $@";
$class->CanonicalizeGeneratedPaths();
$BasePath = Cwd::realpath($BasePath);
for my $path (
- qw/EtcPath BinPath SbinPath VarPath LocalPath LocalEtcPath
- LocalLibPath LexiconPath LocalLexiconPath PluginPath
- LocalPluginPath MasonComponentRoot MasonLocalComponentRoot
+ qw/EtcPath BinPath SbinPath VarPath LocalPath StaticPath LocalEtcPath
+ LocalLibPath LexiconPath LocalLexiconPath PluginPath FontPath
+ LocalPluginPath LocalStaticPath MasonComponentRoot MasonLocalComponentRoot
MasonDataDir MasonSessionDir/
)
{
=head2 AddJavaScript
-helper method to add js files to C<JSFiles> config.
-to add extra js files, you can add the following line
-in the plugin's main file:
+Helper method to add JS files to the C<@JSFiles> config at runtime.
+
+To add files, you can add the following line to your extension's main C<.pm>
+file:
RT->AddJavaScript( 'foo.js', 'bar.js' );
+Files are expected to be in a static root in a F<js/> directory, such as
+F<static/js/> in your extension or F<local/static/js/> for local overlays.
+
=cut
sub AddJavaScript {
=head2 AddStyleSheets
-helper method to add css files to C<CSSFiles> config
+Helper method to add CSS files to the C<@CSSFiles> config at runtime.
-to add extra css files, you can add the following line
-in the plugin's main file:
+To add files, you can add the following line to your extension's main C<.pm>
+file:
RT->AddStyleSheets( 'foo.css', 'bar.css' );
+Files are expected to be in a static root in a F<css/> directory, such as
+F<static/css/> in your extension or F<local/static/css/> for local
+overlays.
+
=cut
sub AddStyleSheets {
return RT->Config->Get('CSSFiles');
}
+=head2 Deprecated
+
+Notes that a particular call path is deprecated, and will be removed in
+a particular release. Puts a warning in the logs indicating such, along
+with a stack trace.
+
+Optional arguments include:
+
+=over
+
+=item Remove
+
+The release which is slated to remove the method or component
+
+=item Instead
+
+A suggestion of what to use in place of the deprecated API
+
+=item Arguments
+
+Used if not the entire method is being removed, merely a manner of
+calling it; names the arguments which are deprecated.
+
+=item Message
+
+Overrides the auto-built phrasing of C<Calling function ____ is
+deprecated> with a custom message.
+
+=item Object
+
+An L<RT::Record> object to print the class and numeric id of. Useful if the
+admin will need to hunt down a particular object to fix the deprecation
+warning.
+
+=back
+
+=cut
+
+sub Deprecated {
+ my $class = shift;
+ my %args = (
+ Arguments => undef,
+ Remove => undef,
+ Instead => undef,
+ Message => undef,
+ Stack => 1,
+ LogLevel => "warn",
+ @_,
+ );
+
+ my ($function) = (caller(1))[3];
+ my $stack;
+ if ($function eq "HTML::Mason::Commands::__ANON__") {
+ eval { HTML::Mason::Exception->throw() };
+ my $error = $@;
+ my $info = $error->analyze_error;
+ $function = "Mason component ".$info->{frames}[0]->filename;
+ $stack = join("\n", map { sprintf("\t[%s:%d]", $_->filename, $_->line) } @{$info->{frames}});
+ } else {
+ $function = "function $function";
+ $stack = Carp::longmess();
+ }
+ $stack =~ s/^.*?\n//; # Strip off call to ->Deprecated
+
+ my $msg;
+ if ($args{Message}) {
+ $msg = $args{Message};
+ } elsif ($args{Arguments}) {
+ $msg = "Calling $function with $args{Arguments} is deprecated";
+ } else {
+ $msg = "The $function is deprecated";
+ }
+ $msg .= ", and will be removed in RT $args{Remove}"
+ if $args{Remove};
+ $msg .= ".";
+
+ $msg .= " You should use $args{Instead} instead."
+ if $args{Instead};
+
+ $msg .= sprintf " Object: %s #%d.", blessed($args{Object}), $args{Object}->id
+ if $args{Object};
+
+ $msg .= " Call stack:\n$stack" if $args{Stack};
+
+ my $loglevel = $args{LogLevel};
+ RT->Logger->$loglevel($msg);
+}
+
=head1 BUGS
Please report them to rt-bugs@bestpractical.com, if you know what's
broken and have at least some idea of what needs to be fixed.
-If you're not sure what's going on, report them rt-devel@lists.bestpractical.com.
+If you're not sure what's going on, start a discussion in the RT Developers
+category on the community forum at L<https://forum.bestpractical.com> or
+send email to sales@bestpractical.com for professional assistance.
=head1 SEE ALSO