summaryrefslogtreecommitdiff
path: root/rt/lib/RT/Config.pm
diff options
context:
space:
mode:
Diffstat (limited to 'rt/lib/RT/Config.pm')
-rw-r--r--rt/lib/RT/Config.pm605
1 files changed, 411 insertions, 194 deletions
diff --git a/rt/lib/RT/Config.pm b/rt/lib/RT/Config.pm
index 8d307398c..08844f50d 100644
--- a/rt/lib/RT/Config.pm
+++ b/rt/lib/RT/Config.pm
@@ -51,8 +51,10 @@ package RT::Config;
use strict;
use warnings;
-
+use 5.010;
use File::Spec ();
+use Symbol::Global::Name;
+use List::MoreUtils 'uniq';
=head1 NAME
@@ -107,7 +109,7 @@ Keyed by config name, there are several properties that
can be set for each config optin:
Section - What header this option should be grouped
- under on the user Settings page
+ under on the user Preferences page
Overridable - Can users change this option
SortOrder - Within a Section, how should the options be sorted
for display to the user
@@ -122,6 +124,11 @@ can be set for each config optin:
Callback - subref that receives no arguments. It returns
a hashref of items that are added to the rest
of the WidgetArguments
+ PostSet - subref passed the RT::Config object and the current and
+ previous setting of the config option. This is called well
+ before much of RT's subsystems are initialized, so what you
+ can do here is pretty limited. It's mostly useful for
+ effecting the value of other config options early.
PostLoadCheck - subref passed the RT::Config object and the current
setting of the config option. Can make further checks
(such as seeing if a library is installed) and then change
@@ -133,7 +140,8 @@ can be set for each config optin:
=cut
-our %META = (
+our %META;
+%META = (
# General user overridable options
DefaultQueue => {
Section => 'General',
@@ -171,8 +179,9 @@ our %META = (
Widget => '/Widgets/Form/Select',
WidgetArguments => {
Description => 'Username format', # loc
- Values => [qw(concise verbose)],
+ Values => [qw(role concise verbose)],
ValuesLabel => {
+ role => 'Privileged: usernames; Unprivileged: names and email addresses', # loc
concise => 'Short usernames', # loc
verbose => 'Name and email address', # loc
},
@@ -195,27 +204,54 @@ our %META = (
Widget => '/Widgets/Form/Select',
WidgetArguments => {
Description => 'Theme', #loc
- # XXX: we need support for 'get values callback'
- Values => [qw(web2 freeside2.1 freeside3 aileron ballard)],
+ Callback => sub {
+ state @stylesheets;
+ unless (@stylesheets) {
+ for my $static_path ( RT::Interface::Web->StaticRoots ) {
+ my $css_path =
+ File::Spec->catdir( $static_path, 'css' );
+ next unless -d $css_path;
+ if ( opendir my $dh, $css_path ) {
+ push @stylesheets, grep {
+ $_ ne 'base' && -e File::Spec->catfile( $css_path, $_, 'main.css' )
+ } readdir $dh;
+ }
+ else {
+ RT->Logger->error("Can't read $css_path: $!");
+ }
+ }
+ @stylesheets = sort { lc $a cmp lc $b } uniq @stylesheets;
+ }
+ return { Values => \@stylesheets };
+ },
},
PostLoadCheck => sub {
my $self = shift;
my $value = $self->Get('WebDefaultStylesheet');
- my @comp_roots = RT::Interface::Web->ComponentRoots;
- for my $comp_root (@comp_roots) {
- return if -d $comp_root.'/NoAuth/css/'.$value;
+ my @roots = RT::Interface::Web->StaticRoots;
+ for my $root (@roots) {
+ return if -d "$root/css/$value";
}
$RT::Logger->warning(
"The default stylesheet ($value) does not exist in this instance of RT. "
- . "Defaulting to freeside3."
+ . "Defaulting to freeside4."
);
- #$self->Set('WebDefaultStylesheet', 'aileron');
- $self->Set('WebDefaultStylesheet', 'freeside3');
+ $self->Set('WebDefaultStylesheet', 'freeside4');
},
},
+ TimeInICal => {
+ Section => 'General',
+ Overridable => 1,
+ SortOrder => 5,
+ Widget => '/Widgets/Form/Boolean',
+ WidgetArguments => {
+ Description => 'Include time in iCal feed events?', # loc
+ Hints => 'Formats iCal feed events with date and time' #loc
+ }
+ },
UseSideBySideLayout => {
Section => 'Ticket composition',
Overridable => 1,
@@ -261,17 +297,6 @@ our %META = (
Description => 'Message box height', #loc
},
},
- MessageBoxWrap => {
- Section => 'Ticket composition', #loc
- Overridable => 1,
- SortOrder => 8.1,
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => 'Message box wrapping', #loc
- Values => [qw(SOFT HARD)],
- Hints => "When the WYSIWYG editor is not enabled, this setting determines whether automatic line wraps in the ticket message box are sent to RT or not.", # loc
- },
- },
DefaultTimeUnitsToHours => {
Section => 'Ticket composition', #loc
Overridable => 1,
@@ -324,6 +349,16 @@ our %META = (
},
# User overridable options for Ticket displays
+ PreferRichText => {
+ Section => 'Ticket display', # loc
+ Overridable => 1,
+ SortOrder => 0.9,
+ Widget => '/Widgets/Form/Boolean',
+ WidgetArguments => {
+ Description => 'Display messages in rich text if available', # loc
+ Hints => 'Rich text (HTML) shows formatting such as colored text, bold, italics, and more', # loc
+ },
+ },
MaxInlineBody => {
Section => 'Ticket display', #loc
Overridable => 1,
@@ -344,13 +379,19 @@ our %META = (
Description => 'Show oldest history first', #loc
},
},
- DeferTransactionLoading => {
+ ShowHistory => {
Section => 'Ticket display',
Overridable => 1,
SortOrder => 3,
- Widget => '/Widgets/Form/Boolean',
+ Widget => '/Widgets/Form/Select',
WidgetArguments => {
- Description => 'Hide ticket history by default', #loc
+ Description => 'Show history', #loc
+ Values => [qw(delay click always)],
+ ValuesLabel => {
+ delay => "after the rest of the page loads", #loc
+ click => "after clicking a link", #loc
+ always => "immediately", #loc
+ },
},
},
ShowUnreadMessageNotifications => {
@@ -364,13 +405,20 @@ our %META = (
},
PlainTextPre => {
- Section => 'Ticket display',
- Overridable => 1,
- SortOrder => 5,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'add <pre> tag around plain text attachments', #loc
- Hints => "Use this to protect the format of plain text" #loc
+ PostSet => sub {
+ my $self = shift;
+ my $value = shift;
+ $self->SetFromConfig(
+ Option => \'PlainTextMono',
+ Value => [$value],
+ %{$self->Meta('PlainTextPre')->{'Source'}}
+ );
+ },
+ PostLoadCheck => sub {
+ my $self = shift;
+ # XXX: deprecated, remove in 4.4
+ $RT::Logger->info("You set \$PlainTextPre in your config, which has been removed in favor of \$PlainTextMono. Please update your config.")
+ if $self->Meta('PlainTextPre')->{'Source'}{'Package'};
},
},
PlainTextMono => {
@@ -379,18 +427,8 @@ our %META = (
SortOrder => 5,
Widget => '/Widgets/Form/Boolean',
WidgetArguments => {
- Description => 'display wrapped and formatted plain text attachments', #loc
- Hints => 'Use css rules to display text monospaced and with formatting preserved, but wrap as needed. This does not work well with IE6 and you should use the previous option', #loc
- },
- },
- DisplayAfterQuickCreate => {
- Section => 'Ticket display',
- Overridable => 1,
- SortOrder => 6,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'On Quick Create, redirect to ticket display', #loc
- #Hints => '', #loc
+ Description => 'Display plain-text attachments in fixed-width font', #loc
+ Hints => 'Display all plain-text attachments in a monospace font with formatting preserved, but wrapping as needed.', #loc
},
},
MoreAboutRequestorTicketList => {
@@ -399,7 +437,7 @@ our %META = (
SortOrder => 6,
Widget => '/Widgets/Form/Select',
WidgetArguments => {
- Description => q|What tickets to display in the 'More about requestor' box|, #loc
+ Description => 'What tickets to display in the "More about requestor" box', #loc
Values => [qw(Active Inactive All None)],
ValuesLabel => {
Active => "Show the Requestor's 10 highest priority active tickets", #loc
@@ -415,7 +453,7 @@ our %META = (
SortOrder => 7,
Widget => '/Widgets/Form/Boolean',
WidgetArguments => {
- Description => q|Show simplified recipient list on ticket update|, #loc
+ Description => "Show simplified recipient list on ticket update", #loc
},
},
DisplayTicketAfterQuickCreate => {
@@ -424,9 +462,18 @@ our %META = (
SortOrder => 8,
Widget => '/Widgets/Form/Boolean',
WidgetArguments => {
- Description => q{Display ticket after "Quick Create"}, #loc
+ Description => 'Display ticket after "Quick Create"', #loc
},
},
+ QuoteFolding => {
+ Section => 'Ticket display',
+ Overridable => 1,
+ SortOrder => 9,
+ Widget => '/Widgets/Form/Boolean',
+ WidgetArguments => {
+ Description => 'Enable quote folding?' # loc
+ }
+ },
# User overridable locale options
DateTimeFormat => {
@@ -513,6 +560,10 @@ our %META = (
},
# Internal config options
+ DatabaseExtraDSN => {
+ Type => 'HASH',
+ },
+
FullTextSearch => {
Type => 'HASH',
PostLoadCheck => sub {
@@ -540,11 +591,26 @@ our %META = (
$RT::Logger->error("No Table set for full-text index; disabling");
$v->{Enable} = $v->{Indexed} = 0;
} elsif ($v->{'Table'} eq "Attachments") {
- $RT::Logger->error("Table for full-text index is set to Attachments, not SphinxSE table; disabling");
+ $RT::Logger->error("Table for full-text index is set to Attachments, not FTS table; disabling");
$v->{Enable} = $v->{Indexed} = 0;
- } elsif (not $v->{'MaxMatches'}) {
- $RT::Logger->warn("No MaxMatches set for full-text index; defaulting to 10000");
- $v->{MaxMatches} = 10_000;
+ } else {
+ my (undef, $create) = eval { $RT::Handle->dbh->selectrow_array("SHOW CREATE TABLE " . $v->{Table}); };
+ my ($engine) = ($create||'') =~ /engine=(\S+)/i;
+ if (not $create) {
+ $RT::Logger->error("External table ".$v->{Table}." does not exist");
+ $v->{Enable} = $v->{Indexed} = 0;
+ } elsif (lc $engine eq "sphinx") {
+ # External Sphinx indexer
+ $v->{Sphinx} = 1;
+ unless ($v->{'MaxMatches'}) {
+ $RT::Logger->warn("No MaxMatches set for full-text index; defaulting to 10000");
+ $v->{MaxMatches} = 10_000;
+ }
+ } else {
+ # Internal, one-column table
+ $v->{Column} = 'Content';
+ $v->{Engine} = $engine;
+ }
}
} else {
$RT::Logger->error("Indexed full-text-search not supported for $dbtype");
@@ -558,9 +624,7 @@ our %META = (
my $self = shift;
my $value = shift;
return if $value;
- return if $INC{'GraphViz.pm'};
- local $@;
- return if eval {require GraphViz; 1};
+ return if GraphViz->require;
$RT::Logger->debug("You've enabled GraphViz, but we couldn't load the module: $@");
$self->Set( DisableGraphViz => 1 );
},
@@ -571,60 +635,168 @@ our %META = (
my $self = shift;
my $value = shift;
return if $value;
- return if $INC{'GD.pm'};
- local $@;
- return if eval {require GD; 1};
+ return if GD->require;
$RT::Logger->debug("You've enabled GD, but we couldn't load the module: $@");
$self->Set( DisableGD => 1 );
},
},
- MailPlugins => { Type => 'ARRAY' },
- Plugins => {
+ MailCommand => {
+ Type => 'SCALAR',
+ PostLoadCheck => sub {
+ my $self = shift;
+ my $value = $self->Get('MailCommand');
+ return if ref($value) eq "CODE"
+ or $value =~/^(sendmail|sendmailpipe|qmail|testfile|mbox)$/;
+ $RT::Logger->error("Unknown value for \$MailCommand: $value; defaulting to sendmailpipe");
+ $self->Set( MailCommand => 'sendmailpipe' );
+ },
+ },
+ HTMLFormatter => {
+ Type => 'SCALAR',
+ PostLoadCheck => sub { RT::Interface::Email->_HTMLFormatter },
+ },
+ MailPlugins => {
Type => 'ARRAY',
PostLoadCheck => sub {
my $self = shift;
- my $value = $self->Get('Plugins');
- # XXX Remove in RT 4.2
- return unless $value and grep {$_ eq "RT::FM"} @{$value};
- warn 'RTFM has been integrated into core RT, and must be removed from your @Plugins';
+
+ # Make sure Crypt is post-loaded first
+ $META{Crypt}{'PostLoadCheck'}->( $self, $self->Get( 'Crypt' ) );
+
+ my @plugins = $self->Get('MailPlugins');
+ if ( grep $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME', @plugins ) {
+ $RT::Logger->warning(
+ 'Auth::GnuPG and Auth::SMIME (from an extension) have been'
+ .' replaced with Auth::Crypt. @MailPlugins has been adjusted,'
+ .' but should be updated to replace both with Auth::Crypt to'
+ .' silence this warning.'
+ );
+ my %seen;
+ @plugins =
+ grep !$seen{$_}++,
+ grep {
+ $_ eq 'Auth::GnuPG' || $_ eq 'Auth::SMIME'
+ ? 'Auth::Crypt' : $_
+ } @plugins;
+ $self->Set( MailPlugins => @plugins );
+ }
+
+ if ( not @{$self->Get('Crypt')->{Incoming}} and grep $_ eq 'Auth::Crypt', @plugins ) {
+ $RT::Logger->warning("Auth::Crypt enabled in MailPlugins, but no available incoming encryption formats");
+ }
},
},
- GnuPG => { Type => 'HASH' },
- GnuPGOptions => { Type => 'HASH',
+ Crypt => {
+ Type => 'HASH',
+ PostLoadCheck => sub {
+ my $self = shift;
+ require RT::Crypt;
+
+ for my $proto (RT::Crypt->EnabledProtocols) {
+ my $opt = $self->Get($proto);
+ if (not RT::Crypt->LoadImplementation($proto)) {
+ $RT::Logger->error("You enabled $proto, but we couldn't load module RT::Crypt::$proto");
+ $opt->{'Enable'} = 0;
+ } elsif (not RT::Crypt->LoadImplementation($proto)->Probe) {
+ $opt->{'Enable'} = 0;
+ } elsif ($META{$proto}{'PostLoadCheck'}) {
+ $META{$proto}{'PostLoadCheck'}->( $self, $self->Get( $proto ) );
+ }
+
+ }
+
+ my $opt = $self->Get('Crypt');
+ my @enabled = RT::Crypt->EnabledProtocols;
+ my %enabled;
+ $enabled{$_} = 1 for @enabled;
+ $opt->{'Enable'} = scalar @enabled;
+ $opt->{'Incoming'} = [ $opt->{'Incoming'} ]
+ if $opt->{'Incoming'} and not ref $opt->{'Incoming'};
+ if ( $opt->{'Incoming'} && @{ $opt->{'Incoming'} } ) {
+ $RT::Logger->warning("$_ explicitly set as incoming Crypt plugin, but not marked Enabled; removing")
+ for grep {not $enabled{$_}} @{$opt->{'Incoming'}};
+ $opt->{'Incoming'} = [ grep {$enabled{$_}} @{$opt->{'Incoming'}} ];
+ } else {
+ $opt->{'Incoming'} = \@enabled;
+ }
+ if ( $opt->{'Outgoing'} ) {
+ if (not $enabled{$opt->{'Outgoing'}}) {
+ $RT::Logger->warning($opt->{'Outgoing'}.
+ " explicitly set as outgoing Crypt plugin, but not marked Enabled; "
+ . (@enabled ? "using $enabled[0]" : "removing"));
+ }
+ $opt->{'Outgoing'} = $enabled[0] unless $enabled{$opt->{'Outgoing'}};
+ } else {
+ $opt->{'Outgoing'} = $enabled[0];
+ }
+ },
+ },
+ SMIME => {
+ Type => 'HASH',
+ PostLoadCheck => sub {
+ my $self = shift;
+ my $opt = $self->Get('SMIME');
+ return unless $opt->{'Enable'};
+
+ if (exists $opt->{Keyring}) {
+ unless ( File::Spec->file_name_is_absolute( $opt->{Keyring} ) ) {
+ $opt->{Keyring} = File::Spec->catfile( $RT::BasePath, $opt->{Keyring} );
+ }
+ unless (-d $opt->{Keyring} and -r _) {
+ $RT::Logger->info(
+ "RT's SMIME libraries couldn't successfully read your".
+ " configured SMIME keyring directory (".$opt->{Keyring}
+ .").");
+ delete $opt->{Keyring};
+ }
+ }
+
+ if (defined $opt->{CAPath}) {
+ if (-d $opt->{CAPath} and -r _) {
+ # directory, all set
+ } elsif (-f $opt->{CAPath} and -r _) {
+ # file, all set
+ } else {
+ $RT::Logger->warn(
+ "RT's SMIME libraries could not read your configured CAPath (".$opt->{CAPath}.")"
+ );
+ delete $opt->{CAPath};
+ }
+ }
+ },
+ },
+ GnuPG => {
+ Type => 'HASH',
PostLoadCheck => sub {
my $self = shift;
my $gpg = $self->Get('GnuPG');
return unless $gpg->{'Enable'};
+
my $gpgopts = $self->Get('GnuPGOptions');
+ unless ( File::Spec->file_name_is_absolute( $gpgopts->{homedir} ) ) {
+ $gpgopts->{homedir} = File::Spec->catfile( $RT::BasePath, $gpgopts->{homedir} );
+ }
unless (-d $gpgopts->{homedir} && -r _ ) { # no homedir, no gpg
- $RT::Logger->debug(
+ $RT::Logger->info(
"RT's GnuPG libraries couldn't successfully read your".
" configured GnuPG home directory (".$gpgopts->{homedir}
- ."). PGP support has been disabled");
+ ."). GnuPG support has been disabled");
$gpg->{'Enable'} = 0;
return;
}
-
- require RT::Crypt::GnuPG;
- unless (RT::Crypt::GnuPG->Probe()) {
- $RT::Logger->debug(
- "RT's GnuPG libraries couldn't successfully execute gpg.".
- " PGP support has been disabled");
- $gpg->{'Enable'} = 0;
+ if ( grep exists $gpg->{$_}, qw(RejectOnMissingPrivateKey RejectOnBadData AllowEncryptDataInDB) ) {
+ $RT::Logger->warning(
+ "The RejectOnMissingPrivateKey, RejectOnBadData and AllowEncryptDataInDB"
+ ." GnuPG options are now properties of the generic Crypt configuration. You"
+ ." should set them there instead."
+ );
+ delete $gpg->{$_} for qw(RejectOnMissingPrivateKey RejectOnBadData AllowEncryptDataInDB);
}
}
},
+ GnuPGOptions => { Type => 'HASH' },
ReferrerWhitelist => { Type => 'ARRAY' },
- ResolveDefaultUpdateType => {
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
- return unless $value;
- $RT::Logger->info('The ResolveDefaultUpdateType config option has been deprecated. '.
- 'You can change the site default in your %Lifecycles config.');
- }
- },
WebPath => {
PostLoadCheck => sub {
my $self = shift;
@@ -762,35 +934,88 @@ our %META = (
}
},
},
+ LogToScreen => {
+ Deprecated => {
+ Instead => 'LogToSTDERR',
+ Remove => '4.4',
+ },
+ },
+ UserAutocompleteFields => {
+ Deprecated => {
+ Instead => 'UserSearchFields',
+ Remove => '4.4',
+ },
+ },
+ CustomFieldGroupings => {
+ Type => 'HASH',
+ PostLoadCheck => sub {
+ my $config = shift;
+ # use scalar context intentionally to avoid not a hash error
+ my $groups = $config->Get('CustomFieldGroupings') || {};
- ActiveStatus => {
- Type => 'ARRAY',
- PostLoadCheck => sub {
- my $self = shift;
- return unless shift;
- # XXX Remove in RT 4.2
- warn <<EOT;
-The ActiveStatus configuration has been replaced by the new Lifecycles
-functionality. You should set the 'active' property of the 'default'
-lifecycle and add transition rules; see RT_Config.pm for documentation.
-EOT
+ unless (ref($groups) eq 'HASH') {
+ RT->Logger->error("Config option \%CustomFieldGroupings is a @{[ref $groups]} not a HASH; ignoring");
+ $groups = {};
+ }
+
+ for my $class (keys %$groups) {
+ my @h;
+ if (ref($groups->{$class}) eq 'HASH') {
+ push @h, $_, $groups->{$class}->{$_}
+ for sort {lc($a) cmp lc($b)} keys %{ $groups->{$class} };
+ } elsif (ref($groups->{$class}) eq 'ARRAY') {
+ @h = @{ $groups->{$class} };
+ } else {
+ RT->Logger->error("Config option \%CustomFieldGroupings{$class} is not a HASH or ARRAY; ignoring");
+ delete $groups->{$class};
+ next;
+ }
+
+ $groups->{$class} = [];
+ while (@h) {
+ my $group = shift @h;
+ my $ref = shift @h;
+ if (ref($ref) eq 'ARRAY') {
+ push @{$groups->{$class}}, $group => $ref;
+ } else {
+ RT->Logger->error("Config option \%CustomFieldGroupings{$class}{$group} is not an ARRAY; ignoring");
+ }
+ }
+ }
+ $config->Set( CustomFieldGroupings => %$groups );
},
},
- InactiveStatus => {
- Type => 'ARRAY',
- PostLoadCheck => sub {
- my $self = shift;
- return unless shift;
- # XXX Remove in RT 4.2
- warn <<EOT;
-The InactiveStatus configuration has been replaced by the new Lifecycles
-functionality. You should set the 'inactive' property of the 'default'
-lifecycle and add transition rules; see RT_Config.pm for documentation.
-EOT
+ ChartColors => {
+ Type => 'ARRAY',
+ },
+ WebExternalAuth => { Deprecated => { Instead => 'WebRemoteUserAuth', Remove => '4.4' }},
+ WebExternalAuthContinuous => { Deprecated => { Instead => 'WebRemoteUserContinuous', Remove => '4.4' }},
+ WebFallbackToInternalAuth => { Deprecated => { Instead => 'WebFallbackToRTLogin', Remove => '4.4' }},
+ WebExternalGecos => { Deprecated => { Instead => 'WebRemoteUserGecos', Remove => '4.4' }},
+ WebExternalAuto => { Deprecated => { Instead => 'WebRemoteUserAutocreate', Remove => '4.4' }},
+ AutoCreate => { Deprecated => { Instead => 'UserAutocreateDefaultsOnLogin', Remove => '4.4' }},
+ LogoImageHeight => {
+ Deprecated => {
+ LogLevel => "info",
+ Message => "The LogoImageHeight configuration option did not affect display, and has been removed; please remove it from your RT_SiteConfig.pm",
+ },
+ },
+ LogoImageWidth => {
+ Deprecated => {
+ LogLevel => "info",
+ Message => "The LogoImageWidth configuration option did not affect display, and has been removed; please remove it from your RT_SiteConfig.pm",
+ },
+ },
+ DatabaseRequireSSL => {
+ Deprecated => {
+ Remove => '4.4',
+ LogLevel => "info",
+ Message => "The DatabaseRequireSSL configuration option did not enable SSL connections to the database, and has been removed; please remove it from your RT_SiteConfig.pm. Use DatabaseExtraDSN to accomplish the same purpose.",
},
},
);
my %OPTIONS = ();
+my @LOADED_CONFIGS = ();
=head1 METHODS
@@ -812,19 +1037,6 @@ sub _Init {
return;
}
-=head2 InitConfig
-
-Do nothin right now.
-
-=cut
-
-sub InitConfig {
- my $self = shift;
- my %args = ( File => '', @_ );
- $args{'File'} =~ s/(?<=Config)(?=\.pm$)/Meta/;
- return 1;
-}
-
=head2 LoadConfigs
Load all configs. First of all load RT's config then load
@@ -836,11 +1048,9 @@ Takes no arguments.
sub LoadConfigs {
my $self = shift;
- $self->InitConfig( File => 'RT_Config.pm' );
$self->LoadConfig( File => 'RT_Config.pm' );
my @configs = $self->Configs;
- $self->InitConfig( File => $_ ) foreach @configs;
$self->LoadConfig( File => $_ ) foreach @configs;
return;
}
@@ -868,9 +1078,13 @@ sub LoadConfig {
and my $site_config = $ENV{RT_SITE_CONFIG} )
{
$self->_LoadConfig( %args, File => $site_config );
+ # to allow load siteconfig again and again in case it's updated
+ delete $INC{ $site_config };
} else {
$self->_LoadConfig(%args);
+ delete $INC{$args{'File'}};
}
+
$args{'File'} =~ s/Site(?=Config\.pm$)//;
$self->_LoadConfig(%args);
return 1;
@@ -903,6 +1117,20 @@ sub _LoadConfig {
Extension => $is_ext,
);
};
+ local *Plugin = sub {
+ my (@new_plugins) = @_;
+ @new_plugins = map {s/-/::/g if not /:/; $_} @new_plugins;
+ my ( $pack, $file, $line ) = caller;
+ return $self->SetFromConfig(
+ Option => \@RT::Plugins,
+ Value => [@RT::Plugins, @new_plugins],
+ Package => $pack,
+ File => $file,
+ Line => $line,
+ SiteConfig => $is_site,
+ Extension => $is_ext,
+ );
+ };
my @etc_dirs = ($RT::LocalEtcPath);
push @etc_dirs, RT->PluginDirs('etc') if $is_ext;
push @etc_dirs, $RT::EtcPath, @INC;
@@ -953,6 +1181,14 @@ EOF
my $errormessage = sprintf( $message,
$file_path, $fileusername, $filegroup, $filegroup );
die "$errormessage\n$@";
+ } else {
+ # Loaded successfully
+ push @LOADED_CONFIGS, {
+ as => $args{'File'},
+ filename => $INC{ $args{'File'} },
+ extension => $is_ext,
+ site => $is_site,
+ };
}
return 1;
}
@@ -989,6 +1225,40 @@ sub Configs {
return @configs;
}
+=head2 LoadedConfigs
+
+Returns a list of hashrefs, one for each config file loaded. The keys of the
+hashes are:
+
+=over 4
+
+=item as
+
+Name this config file was loaded as (relative filename usually).
+
+=item filename
+
+The full path and filename.
+
+=item extension
+
+The "extension" part of the filename. For example, the file C<RTIR_Config.pm>
+will have an C<extension> value of C<RTIR>.
+
+=item site
+
+True if the file is considered a site-level override. For example, C<site>
+will be false for C<RT_Config.pm> and true for C<RT_SiteConfig.pm>.
+
+=back
+
+=cut
+
+sub LoadedConfigs {
+ # Copy to avoid the caller changing our internal data
+ return map { \%$_ } @LOADED_CONFIGS
+}
+
=head2 Get
Takes name of the option as argument and returns its current value.
@@ -1080,6 +1350,24 @@ sub Set {
{no warnings 'once'; no strict 'refs'; ${"RT::$name"} = $OPTIONS{$name}; }
}
$META{$name}->{'Type'} = $type;
+ $META{$name}->{'PostSet'}->($self, $OPTIONS{$name}, $old)
+ if $META{$name}->{'PostSet'};
+ if ($META{$name}->{'Deprecated'}) {
+ my %deprecated = %{$META{$name}->{'Deprecated'}};
+ my $new_var = $deprecated{Instead} || '';
+ $self->SetFromConfig(
+ Option => \$new_var,
+ Value => [$OPTIONS{$name}],
+ %{$self->Meta($name)->{'Source'}}
+ ) if $new_var;
+ $META{$name}->{'PostLoadCheck'} ||= sub {
+ RT->Deprecated(
+ Message => "Configuration option $name is deprecated",
+ Stack => 0,
+ %deprecated,
+ );
+ };
+ }
return $self->_ReturnValue( $old, $type );
}
@@ -1115,7 +1403,7 @@ sub SetFromConfig {
my $opt = $args{'Option'};
my $type;
- my $name = $self->__GetNameByRef($opt);
+ my $name = Symbol::Global::Name->find($opt);
if ($name) {
$type = ref $opt;
$name =~ s/.*:://;
@@ -1175,77 +1463,6 @@ sub SetFromConfig {
return 1;
}
- our %REF_SYMBOLS = (
- SCALAR => '$',
- ARRAY => '@',
- HASH => '%',
- CODE => '&',
- );
-
-{
- my $last_pack = '';
-
- sub __GetNameByRef {
- my $self = shift;
- my $ref = shift;
- my $pack = shift;
- if ( !$pack && $last_pack ) {
- my $tmp = $self->__GetNameByRef( $ref, $last_pack );
- return $tmp if $tmp;
- }
- $pack ||= 'main::';
- $pack .= '::' unless substr( $pack, -2 ) eq '::';
-
- no strict 'refs';
- my $name = undef;
-
- # scan $pack's nametable(hash)
- foreach my $k ( keys %{$pack} ) {
-
- # The hash for main:: has a reference to itself
- next if $k eq 'main::';
-
- # if the entry has a trailing '::' then
- # it is a link to another name space
- if ( substr( $k, -2 ) eq '::') {
- $name = $self->__GetNameByRef( $ref, $pack eq 'main::'? $k : $pack.$k );
- return $name if $name;
- }
-
- # entry of the table with references to
- # SCALAR, ARRAY... and other types with
- # the same name
- my $entry = ${$pack}{$k};
- next unless $entry;
-
- # Inlined constants are simplified in the symbol table --
- # namely, when possible, you only get a reference back in
- # $entry, rather than a full GLOB. In 5.10, scalar
- # constants began being inlined this way; starting in 5.20,
- # list constants are also inlined. Notably, ref(GLOB) is
- # undef, but inlined constants are currently either REF,
- # SCALAR, or ARRAY.
- next if ref($entry);
-
- my $ref_type = ref($ref);
-
- # regex/arrayref/hashref/coderef are stored in SCALAR glob
- $ref_type = 'SCALAR' if $ref_type eq 'REF';
-
- my $entry_ref = *{$entry}{ $ref_type };
- next if ref $entry_ref && ref $entry_ref ne ref $ref;
- next unless $entry_ref;
-
- # if references are equal then we've found
- if ( $entry_ref == $ref ) {
- $last_pack = $pack;
- return ( $REF_SYMBOLS{ $ref_type } || '*' ) . $pack . $k;
- }
- }
- return '';
- }
-}
-
=head2 Metadata
@@ -1270,7 +1487,7 @@ sub Sections {
sub Options {
my $self = shift;
my %args = ( Section => undef, Overridable => 1, Sorted => 1, @_ );
- my @res = keys %META;
+ my @res = sort keys %META;
@res = grep( ( $META{$_}->{'Section'} || 'General' ) eq $args{'Section'},
@res