#
# COPYRIGHT:
#
-# This software is Copyright (c) 1996-2013 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;
use File::Spec ();
+use Symbol::Global::Name;
+use List::MoreUtils 'uniq';
=head1 NAME
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
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
=cut
-our %META = (
+our %META;
+%META = (
# General user overridable options
+ RestrictReferrerLogin => {
+ PostLoadCheck => sub {
+ my $self = shift;
+ if (defined($self->Get('RestrictReferrerLogin'))) {
+ RT::Logger->error("The config option 'RestrictReferrerLogin' is incorrect, and should be 'RestrictLoginReferrer' instead.");
+ }
+ },
+ },
DefaultQueue => {
Section => 'General',
Overridable => 1,
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
},
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,
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,
Hints => 'Only for entry, not display', #loc
},
},
+ RefreshIntervals => {
+ Type => 'ARRAY',
+ PostLoadCheck => sub {
+ my $self = shift;
+ my @intervals = $self->Get('RefreshIntervals');
+ if (grep { $_ == 0 } @intervals) {
+ $RT::Logger->warning("Please do not include a 0 value in RefreshIntervals, as that default is already added for you.");
+ }
+ },
+ },
SearchResultsRefreshInterval => {
Section => 'General', #loc
Overridable => 1,
SortOrder => 9,
Widget => '/Widgets/Form/Select',
WidgetArguments => {
- Description => 'Search results refresh interval', #loc
- Values => [qw(0 120 300 600 1200 3600 7200)],
- ValuesLabel => {
- 0 => "Don't refresh search results.", #loc
- 120 => "Refresh search results every 2 minutes.", #loc
- 300 => "Refresh search results every 5 minutes.", #loc
- 600 => "Refresh search results every 10 minutes.", #loc
- 1200 => "Refresh search results every 20 minutes.", #loc
- 3600 => "Refresh search results every 60 minutes.", #loc
- 7200 => "Refresh search results every 120 minutes.", #loc
- },
+ Description => 'Search results refresh interval', #loc
+ Callback => sub {
+ my @values = RT->Config->Get('RefreshIntervals');
+ my %labels = (
+ 0 => "Don't refresh search results.", # loc
+ );
+
+ for my $value (@values) {
+ if ($value % 60 == 0) {
+ $labels{$value} = ['Refresh search results every [quant,_1,minute,minutes].', $value / 60]; # loc
+ }
+ else {
+ $labels{$value} = ['Refresh search results every [quant,_1,second,seconds].', $value]; # loc
+ }
+ }
+
+ unshift @values, 0;
+
+ return { Values => \@values, ValuesLabel => \%labels };
+ },
},
},
Widget => '/Widgets/Form/Select',
WidgetArguments => {
Description => 'Home page refresh interval', #loc
- Values => [qw(0 120 300 600 1200 3600 7200)],
- ValuesLabel => {
- 0 => "Don't refresh home page.", #loc
- 120 => "Refresh home page every 2 minutes.", #loc
- 300 => "Refresh home page every 5 minutes.", #loc
- 600 => "Refresh home page every 10 minutes.", #loc
- 1200 => "Refresh home page every 20 minutes.", #loc
- 3600 => "Refresh home page every 60 minutes.", #loc
- 7200 => "Refresh home page every 120 minutes.", #loc
- },
+ Callback => sub {
+ my @values = RT->Config->Get('RefreshIntervals');
+ my %labels = (
+ 0 => "Don't refresh home page.", # loc
+ );
+
+ for my $value (@values) {
+ if ($value % 60 == 0) {
+ $labels{$value} = ['Refresh home page every [quant,_1,minute,minutes].', $value / 60]; # loc
+ }
+ else {
+ $labels{$value} = ['Refresh home page every [quant,_1,second,seconds].', $value]; # loc
+ }
+ }
+
+ unshift @values, 0;
+
+ return { Values => \@values, ValuesLabel => \%labels };
+ },
},
},
# 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,
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 => {
},
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 => {
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 => {
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
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 => {
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 => {
'The RTAddressRegexp option is not set in the config.'
.' Not setting this option results in additional SQL queries to'
.' check whether each address belongs to RT or not.'
- .' It is especially important to set this option if RT recieves'
+ .' It is especially important to set this option if RT receives'
.' emails on addresses that are not in the database or config.'
);
} elsif (ref $value and ref $value eq "Regexp") {
},
# Internal config options
+ DatabaseExtraDSN => {
+ Type => 'HASH',
+ },
+
FullTextSearch => {
Type => 'HASH',
PostLoadCheck => sub {
$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");
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 );
},
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");
+ }
+ },
+ },
+ 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' },
- GnuPGOptions => { Type => 'HASH',
+ 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;
}
},
},
+ 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
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
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;
}
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;
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;
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;
}
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.
my $res;
if ( $user && $user->id && $META{$name}->{'Overridable'} ) {
- $user = $user->UserObj if $user->isa('RT::CurrentUser');
my $prefs = $user->Preferences($RT::System);
$res = $prefs->{$name} if $prefs;
}
{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 );
}
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/.*:://;
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;
-
- # get entry for type we are looking for
- # XXX skip references to scalars or other references.
- # Otherwie 5.10 goes boom. maybe we should skip any
- # reference
- next if ref($entry) eq 'SCALAR' || ref($entry) eq 'REF';
-
- 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
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