+++ /dev/null
-# BEGIN BPS TAGGED BLOCK {{{
-#
-# COPYRIGHT:
-#
-# This software is Copyright (c) 1996-2014 Best Practical Solutions, LLC
-# <sales@bestpractical.com>
-#
-# (Except where explicitly superseded by other copyright notices)
-#
-#
-# LICENSE:
-#
-# This work is made available to you under the terms of Version 2 of
-# the GNU General Public License. A copy of that license should have
-# been provided with this software, but in any event can be snarfed
-# from www.gnu.org.
-#
-# This work is distributed in the hope that it will be useful, but
-# WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this program; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
-# 02110-1301 or visit their web page on the internet at
-# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
-#
-#
-# CONTRIBUTION SUBMISSION POLICY:
-#
-# (The following paragraph is not intended to limit the rights granted
-# to you to modify and distribute this software under the terms of
-# the GNU General Public License and is only of importance to you if
-# you choose to contribute your changes and enhancements to the
-# community by submitting them to Best Practical Solutions, LLC.)
-#
-# By intentionally submitting any modifications, corrections or
-# derivatives to this work, or any other work intended for use with
-# Request Tracker, to Best Practical Solutions, LLC, you confirm that
-# you are the copyright holder for those contributions and you grant
-# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
-# royalty-free, perpetual, license to use, copy, create derivative
-# works based on those contributions, and sublicense and distribute
-# those contributions and any derivatives thereof.
-#
-# END BPS TAGGED BLOCK }}}
-
-package RT::Config;
-
-use strict;
-use warnings;
-
-
-use File::Spec ();
-
-=head1 NAME
-
- RT::Config - RT's config
-
-=head1 SYNOPSYS
-
- # get config object
- use RT::Config;
- my $config = RT::Config->new;
- $config->LoadConfigs;
-
- # get or set option
- my $rt_web_path = $config->Get('WebPath');
- $config->Set(EmailOutputEncoding => 'latin1');
-
- # get config object from RT package
- use RT;
- RT->LoadConfig;
- my $config = RT->Config;
-
-=head1 DESCRIPTION
-
-C<RT::Config> class provide access to RT's and RT extensions' config files.
-
-RT uses two files for site configuring:
-
-First file is F<RT_Config.pm> - core config file. This file is shipped
-with RT distribution and contains default values for all available options.
-B<You should never edit this file.>
-
-Second file is F<RT_SiteConfig.pm> - site config file. You can use it
-to customize your RT instance. In this file you can override any option
-listed in core config file.
-
-RT extensions could also provide thier config files. Extensions should
-use F<< <NAME>_Config.pm >> and F<< <NAME>_SiteConfig.pm >> names for
-config files, where <NAME> is extension name.
-
-B<NOTE>: All options from RT's config and extensions' configs are saved
-in one place and thus extension could override RT's options, but it is not
-recommended.
-
-=cut
-
-=head2 %META
-
-Hash of Config options that may be user overridable
-or may require more logic than should live in RT_*Config.pm
-
-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
- Overridable - Can users change this option
- SortOrder - Within a Section, how should the options be sorted
- for display to the user
- Widget - Mason component path to widget that should be used
- to display this config option
- WidgetArguments - An argument hash passed to the WIdget
- Description - Friendly description to show the user
- Values - Arrayref of options (for select Widget)
- ValuesLabel - Hashref, key is the Value from the Values
- list, value is a user friendly description
- of the value
- Callback - subref that receives no arguments. It returns
- a hashref of items that are added to the rest
- of the WidgetArguments
- 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
- the setting of this or other options in the Config using
- the RT::Config option.
- Obfuscate - subref passed the RT::Config object, current setting of the config option
- and a user object, can return obfuscated value. it's called in
- RT->Config->GetObfuscated()
-
-=cut
-
-our %META = (
- # General user overridable options
- DefaultQueue => {
- Section => 'General',
- Overridable => 1,
- SortOrder => 1,
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => 'Default queue', #loc
- Callback => sub {
- my $ret = { Values => [], ValuesLabel => {}};
- my $q = RT::Queues->new($HTML::Mason::Commands::session{'CurrentUser'});
- $q->UnLimit;
- while (my $queue = $q->Next) {
- next unless $queue->CurrentUserHasRight("CreateTicket");
- push @{$ret->{Values}}, $queue->Id;
- $ret->{ValuesLabel}{$queue->Id} = $queue->Name;
- }
- return $ret;
- },
- }
- },
- RememberDefaultQueue => {
- Section => 'General',
- Overridable => 1,
- SortOrder => 2,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Remember default queue' # loc
- }
- },
- UsernameFormat => {
- Section => 'General',
- Overridable => 1,
- SortOrder => 3,
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => 'Username format', # loc
- Values => [qw(concise verbose)],
- ValuesLabel => {
- concise => 'Short usernames', # loc
- verbose => 'Name and email address', # loc
- },
- },
- },
- AutocompleteOwners => {
- Section => 'General',
- Overridable => 1,
- SortOrder => 3.1,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Use autocomplete to find owners?', # loc
- Hints => 'Replaces the owner dropdowns with textboxes' #loc
- }
- },
- WebDefaultStylesheet => {
- Section => 'General', #loc
- Overridable => 1,
- SortOrder => 4,
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => 'Theme', #loc
- # XXX: we need support for 'get values callback'
- Values => [qw(web2 freeside2.1 freeside3 aileron ballard)],
- },
- 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;
- }
-
- $RT::Logger->warning(
- "The default stylesheet ($value) does not exist in this instance of RT. "
- . "Defaulting to freeside3."
- );
-
- #$self->Set('WebDefaultStylesheet', 'aileron');
- $self->Set('WebDefaultStylesheet', 'freeside3');
- },
- },
- UseSideBySideLayout => {
- Section => 'Ticket composition',
- Overridable => 1,
- SortOrder => 5,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Use a two column layout for create and update forms?' # loc
- }
- },
- MessageBoxRichText => {
- Section => 'Ticket composition',
- Overridable => 1,
- SortOrder => 5.1,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'WYSIWYG message composer' # loc
- }
- },
- MessageBoxRichTextHeight => {
- Section => 'Ticket composition',
- Overridable => 1,
- SortOrder => 6,
- Widget => '/Widgets/Form/Integer',
- WidgetArguments => {
- Description => 'WYSIWYG composer height', # loc
- }
- },
- MessageBoxWidth => {
- Section => 'Ticket composition',
- Overridable => 1,
- SortOrder => 7,
- Widget => '/Widgets/Form/Integer',
- WidgetArguments => {
- Description => 'Message box width', #loc
- },
- },
- MessageBoxHeight => {
- Section => 'Ticket composition',
- Overridable => 1,
- SortOrder => 8,
- Widget => '/Widgets/Form/Integer',
- WidgetArguments => {
- 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,
- SortOrder => 9,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Enter time in hours by default', #loc
- Hints => 'Only for entry, not display', #loc
- },
- },
- 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
- },
- },
- },
-
- # User overridable options for RT at a glance
- HomePageRefreshInterval => {
- Section => 'RT at a glance', #loc
- Overridable => 1,
- SortOrder => 2,
- 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
- },
- },
- },
-
- # User overridable options for Ticket displays
- MaxInlineBody => {
- Section => 'Ticket display', #loc
- Overridable => 1,
- SortOrder => 1,
- Widget => '/Widgets/Form/Integer',
- WidgetArguments => {
- Description => 'Maximum inline message length', #loc
- Hints =>
- "Length in characters; Use '0' to show all messages inline, regardless of length" #loc
- },
- },
- OldestTransactionsFirst => {
- Section => 'Ticket display',
- Overridable => 1,
- SortOrder => 2,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Show oldest history first', #loc
- },
- },
- DeferTransactionLoading => {
- Section => 'Ticket display',
- Overridable => 1,
- SortOrder => 3,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Hide ticket history by default', #loc
- },
- },
- ShowUnreadMessageNotifications => {
- Section => 'Ticket display',
- Overridable => 1,
- SortOrder => 4,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Notify me of unread messages', #loc
- },
-
- },
- 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
- },
- },
- PlainTextMono => {
- Section => 'Ticket display',
- Overridable => 1,
- 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
- },
- },
- MoreAboutRequestorTicketList => {
- Section => 'Ticket display', #loc
- Overridable => 1,
- SortOrder => 6,
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => q|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
- Inactive => "Show the Requestor's 10 highest priority inactive tickets", #loc
- All => "Show the Requestor's 10 highest priority tickets", #loc
- None => "Show no tickets for the Requestor", #loc
- },
- },
- },
- SimplifiedRecipients => {
- Section => 'Ticket display', #loc
- Overridable => 1,
- SortOrder => 7,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => q|Show simplified recipient list on ticket update|, #loc
- },
- },
- DisplayTicketAfterQuickCreate => {
- Section => 'Ticket display',
- Overridable => 1,
- SortOrder => 8,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => q{Display ticket after "Quick Create"}, #loc
- },
- },
-
- # User overridable locale options
- DateTimeFormat => {
- Section => 'Locale', #loc
- Overridable => 1,
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => 'Date format', #loc
- Callback => sub { my $ret = { Values => [], ValuesLabel => {}};
- my $date = RT::Date->new($HTML::Mason::Commands::session{'CurrentUser'});
- $date->SetToNow;
- foreach my $value ($date->Formatters) {
- push @{$ret->{Values}}, $value;
- $ret->{ValuesLabel}{$value} = $date->Get(
- Format => $value,
- Timezone => 'user',
- );
- }
- return $ret;
- },
- },
- },
-
- RTAddressRegexp => {
- Type => 'SCALAR',
- PostLoadCheck => sub {
- my $self = shift;
- my $value = $self->Get('RTAddressRegexp');
- if (not $value) {
- $RT::Logger->debug(
- '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'
- .' emails on addresses that are not in the database or config.'
- );
- } elsif (ref $value and ref $value eq "Regexp") {
- # Ensure that the regex is case-insensitive; while the
- # local part of email addresses is _technically_
- # case-sensitive, most MTAs don't treat it as such.
- $RT::Logger->warning(
- 'RTAddressRegexp is set to a case-sensitive regular expression.'
- .' This may lead to mail loops with MTAs which treat the'
- .' local part as case-insensitive -- which is most of them.'
- ) if "$value" =~ /^\(\?[a-z]*-([a-z]*):/ and "$1" =~ /i/;
- }
- },
- },
- # User overridable mail options
- EmailFrequency => {
- Section => 'Mail', #loc
- Overridable => 1,
- Default => 'Individual messages',
- Widget => '/Widgets/Form/Select',
- WidgetArguments => {
- Description => 'Email delivery', #loc
- Values => [
- 'Individual messages', #loc
- 'Daily digest', #loc
- 'Weekly digest', #loc
- 'Suspended' #loc
- ]
- }
- },
- NotifyActor => {
- Section => 'Mail', #loc
- Overridable => 1,
- SortOrder => 2,
- Widget => '/Widgets/Form/Boolean',
- WidgetArguments => {
- Description => 'Outgoing mail', #loc
- Hints => 'Should RT send you mail for ticket updates you make?', #loc
- }
- },
-
- # this tends to break extensions that stash links in ticket update pages
- Organization => {
- Type => 'SCALAR',
- PostLoadCheck => sub {
- my ($self,$value) = @_;
- $RT::Logger->error("your \$Organization setting ($value) appears to contain whitespace. Please fix this.")
- if $value =~ /\s/;;
- },
- },
-
- # Internal config options
- FullTextSearch => {
- Type => 'HASH',
- PostLoadCheck => sub {
- my $self = shift;
- my $v = $self->Get('FullTextSearch');
- return unless $v->{Enable} and $v->{Indexed};
- my $dbtype = $self->Get('DatabaseType');
- if ($dbtype eq 'Oracle') {
- if (not $v->{IndexName}) {
- $RT::Logger->error("No IndexName set for full-text index; disabling");
- $v->{Enable} = $v->{Indexed} = 0;
- }
- } elsif ($dbtype eq 'Pg') {
- my $bad = 0;
- if (not $v->{'Column'}) {
- $RT::Logger->error("No Column set for full-text index; disabling");
- $v->{Enable} = $v->{Indexed} = 0;
- } elsif ($v->{'Column'} eq "Content"
- and (not $v->{'Table'} or $v->{'Table'} eq "Attachments")) {
- $RT::Logger->error("Column for full-text index is set to Content, not tsvector column; disabling");
- $v->{Enable} = $v->{Indexed} = 0;
- }
- } elsif ($dbtype eq 'mysql') {
- if (not $v->{'Table'}) {
- $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");
- $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 {
- $RT::Logger->error("Indexed full-text-search not supported for $dbtype");
- $v->{Indexed} = 0;
- }
- },
- },
- DisableGraphViz => {
- Type => 'SCALAR',
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
- return if $value;
- return if $INC{'GraphViz.pm'};
- local $@;
- return if eval {require GraphViz; 1};
- $RT::Logger->debug("You've enabled GraphViz, but we couldn't load the module: $@");
- $self->Set( DisableGraphViz => 1 );
- },
- },
- DisableGD => {
- Type => 'SCALAR',
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
- return if $value;
- return if $INC{'GD.pm'};
- local $@;
- return if eval {require GD; 1};
- $RT::Logger->debug("You've enabled GD, but we couldn't load the module: $@");
- $self->Set( DisableGD => 1 );
- },
- },
- MailPlugins => { Type => 'ARRAY' },
- Plugins => {
- 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';
- },
- },
- GnuPG => { Type => 'HASH' },
- GnuPGOptions => { Type => 'HASH',
- PostLoadCheck => sub {
- my $self = shift;
- my $gpg = $self->Get('GnuPG');
- return unless $gpg->{'Enable'};
- my $gpgopts = $self->Get('GnuPGOptions');
- unless (-d $gpgopts->{homedir} && -r _ ) { # no homedir, no gpg
- $RT::Logger->debug(
- "RT's GnuPG libraries couldn't successfully read your".
- " configured GnuPG home directory (".$gpgopts->{homedir}
- ."). PGP 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;
- }
- }
- },
- 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;
- my $value = shift;
-
- # "In most cases, you should leave $WebPath set to '' (an empty value)."
- return unless $value;
-
- # try to catch someone who assumes that you shouldn't leave this empty
- if ($value eq '/') {
- $RT::Logger->error("For the WebPath config option, use the empty string instead of /");
- return;
- }
-
- # $WebPath requires a leading / but no trailing /, or it can be blank.
- return if $value =~ m{^/.+[^/]$};
-
- if ($value =~ m{/$}) {
- $RT::Logger->error("The WebPath config option requires no trailing slash");
- }
-
- if ($value !~ m{^/}) {
- $RT::Logger->error("The WebPath config option requires a leading slash");
- }
- },
- },
- WebDomain => {
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
-
- if (!$value) {
- $RT::Logger->error("You must set the WebDomain config option");
- return;
- }
-
- if ($value =~ m{^(\w+://)}) {
- $RT::Logger->error("The WebDomain config option must not contain a scheme ($1)");
- return;
- }
-
- if ($value =~ m{(/.*)}) {
- $RT::Logger->error("The WebDomain config option must not contain a path ($1)");
- return;
- }
-
- if ($value =~ m{:(\d*)}) {
- $RT::Logger->error("The WebDomain config option must not contain a port ($1)");
- return;
- }
- },
- },
- WebPort => {
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
-
- if (!$value) {
- $RT::Logger->error("You must set the WebPort config option");
- return;
- }
-
- if ($value !~ m{^\d+$}) {
- $RT::Logger->error("The WebPort config option must be an integer");
- }
- },
- },
- WebBaseURL => {
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
-
- if (!$value) {
- $RT::Logger->error("You must set the WebBaseURL config option");
- return;
- }
-
- if ($value !~ m{^https?://}i) {
- $RT::Logger->error("The WebBaseURL config option must contain a scheme (http or https)");
- }
-
- if ($value =~ m{/$}) {
- $RT::Logger->error("The WebBaseURL config option requires no trailing slash");
- }
-
- if ($value =~ m{^https?://.+?(/[^/].*)}i) {
- $RT::Logger->error("The WebBaseURL config option must not contain a path ($1)");
- }
- },
- },
- WebURL => {
- PostLoadCheck => sub {
- my $self = shift;
- my $value = shift;
-
- if (!$value) {
- $RT::Logger->error("You must set the WebURL config option");
- return;
- }
-
- if ($value !~ m{^https?://}i) {
- $RT::Logger->error("The WebURL config option must contain a scheme (http or https)");
- }
-
- if ($value !~ m{/$}) {
- $RT::Logger->error("The WebURL config option requires a trailing slash");
- }
- },
- },
- EmailInputEncodings => {
- Type => 'ARRAY',
- PostLoadCheck => sub {
- my $self = shift;
- my $value = $self->Get('EmailInputEncodings');
- return unless $value && @$value;
-
- my %seen;
- foreach my $encoding ( grep defined && length, splice @$value ) {
- next if $seen{ $encoding };
- if ( $encoding eq '*' ) {
- unshift @$value, '*';
- next;
- }
-
- my $canonic = Encode::resolve_alias( $encoding );
- unless ( $canonic ) {
- warn "Unknown encoding '$encoding' in \@EmailInputEncodings option";
- }
- elsif ( $seen{ $canonic }++ ) {
- next;
- }
- else {
- push @$value, $canonic;
- }
- }
- },
- },
-
- 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
- },
- },
- 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
- },
- },
-);
-my %OPTIONS = ();
-
-=head1 METHODS
-
-=head2 new
-
-Object constructor returns new object. Takes no arguments.
-
-=cut
-
-sub new {
- my $proto = shift;
- my $class = ref($proto) ? ref($proto) : $proto;
- my $self = bless {}, $class;
- $self->_Init(@_);
- return $self;
-}
-
-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
-extensions' config files in alphabetical order.
-Takes no arguments.
-
-=cut
-
-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;
-}
-
-=head1 LoadConfig
-
-Takes param hash with C<File> field.
-First, the site configuration file is loaded, in order to establish
-overall site settings like hostname and name of RT instance.
-Then, the core configuration file is loaded to set fallback values
-for all settings; it bases some values on settings from the site
-configuration file.
-
-B<Note> that core config file don't change options if site config
-has set them so to add value to some option instead of
-overriding you have to copy original value from core config file.
-
-=cut
-
-sub LoadConfig {
- my $self = shift;
- my %args = ( File => '', @_ );
- $args{'File'} =~ s/(?<!Site)(?=Config\.pm$)/Site/;
- if ( $args{'File'} eq 'RT_SiteConfig.pm'
- and my $site_config = $ENV{RT_SITE_CONFIG} )
- {
- $self->_LoadConfig( %args, File => $site_config );
- } else {
- $self->_LoadConfig(%args);
- }
- $args{'File'} =~ s/Site(?=Config\.pm$)//;
- $self->_LoadConfig(%args);
- return 1;
-}
-
-sub _LoadConfig {
- my $self = shift;
- my %args = ( File => '', @_ );
-
- my ($is_ext, $is_site);
- if ( $args{'File'} eq ($ENV{RT_SITE_CONFIG}||'') ) {
- ($is_ext, $is_site) = ('', 1);
- } else {
- $is_ext = $args{'File'} =~ /^(?!RT_)(?:(.*)_)(?:Site)?Config/ ? $1 : '';
- $is_site = $args{'File'} =~ /SiteConfig/ ? 1 : 0;
- }
-
- eval {
- package RT;
- local *Set = sub(\[$@%]@) {
- my ( $opt_ref, @args ) = @_;
- my ( $pack, $file, $line ) = caller;
- return $self->SetFromConfig(
- Option => $opt_ref,
- Value => [@args],
- 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;
- local @INC = @etc_dirs;
- require $args{'File'};
- };
- if ($@) {
- return 1 if $is_site && $@ =~ /^Can't locate \Q$args{File}/;
- if ( $is_site || $@ !~ /^Can't locate \Q$args{File}/ ) {
- die qq{Couldn't load RT config file $args{'File'}:\n\n$@};
- }
-
- my $username = getpwuid($>);
- my $group = getgrgid($();
-
- my ( $file_path, $fileuid, $filegid );
- foreach ( $RT::LocalEtcPath, $RT::EtcPath, @INC ) {
- my $tmp = File::Spec->catfile( $_, $args{File} );
- ( $fileuid, $filegid ) = ( stat($tmp) )[ 4, 5 ];
- if ( defined $fileuid ) {
- $file_path = $tmp;
- last;
- }
- }
- unless ($file_path) {
- die
- qq{Couldn't load RT config file $args{'File'} as user $username / group $group.\n}
- . qq{The file couldn't be found in $RT::LocalEtcPath and $RT::EtcPath.\n$@};
- }
-
- my $message = <<EOF;
-
-RT couldn't load RT config file %s as:
- user: $username
- group: $group
-
-The file is owned by user %s and group %s.
-
-This usually means that the user/group your webserver is running
-as cannot read the file. Be careful not to make the permissions
-on this file too liberal, because it contains database passwords.
-You may need to put the webserver user in the appropriate group
-(%s) or change permissions be able to run succesfully.
-EOF
-
- my $fileusername = getpwuid($fileuid);
- my $filegroup = getgrgid($filegid);
- my $errormessage = sprintf( $message,
- $file_path, $fileusername, $filegroup, $filegroup );
- die "$errormessage\n$@";
- }
- return 1;
-}
-
-sub PostLoadCheck {
- my $self = shift;
- foreach my $o ( grep $META{$_}{'PostLoadCheck'}, $self->Options( Overridable => undef ) ) {
- $META{$o}->{'PostLoadCheck'}->( $self, $self->Get($o) );
- }
-}
-
-=head2 Configs
-
-Returns list of config files found in local etc, plugins' etc
-and main etc directories.
-
-=cut
-
-sub Configs {
- my $self = shift;
-
- my @configs = ();
- foreach my $path ( $RT::LocalEtcPath, RT->PluginDirs('etc'), $RT::EtcPath ) {
- my $mask = File::Spec->catfile( $path, "*_Config.pm" );
- my @files = glob $mask;
- @files = grep !/^RT_Config\.pm$/,
- grep $_ && /^\w+_Config\.pm$/,
- map { s/^.*[\\\/]//; $_ } @files;
- push @configs, sort @files;
- }
-
- my %seen;
- @configs = grep !$seen{$_}++, @configs;
- return @configs;
-}
-
-=head2 Get
-
-Takes name of the option as argument and returns its current value.
-
-In the case of a user-overridable option, first checks the user's
-preferences before looking for site-wide configuration.
-
-Returns values from RT_SiteConfig, RT_Config and then the %META hash
-of configuration variables's "Default" for this config variable,
-in that order.
-
-Returns different things in scalar and array contexts. For scalar
-options it's not that important, however for arrays and hash it's.
-In scalar context returns references to arrays and hashes.
-
-Use C<scalar> perl's op to force context, especially when you use
-C<(..., Argument => RT->Config->Get('ArrayOpt'), ...)>
-as perl's '=>' op doesn't change context of the right hand argument to
-scalar. Instead use C<(..., Argument => scalar RT->Config->Get('ArrayOpt'), ...)>.
-
-It's also important for options that have no default value(no default
-in F<etc/RT_Config.pm>). If you don't force scalar context then you'll
-get empty list and all your named args will be messed up. For example
-C<(arg1 => 1, arg2 => RT->Config->Get('OptionDoesNotExist'), arg3 => 3)>
-will result in C<(arg1 => 1, arg2 => 'arg3', 3)> what is most probably
-unexpected, or C<(arg1 => 1, arg2 => RT->Config->Get('ArrayOption'), arg3 => 3)>
-will result in C<(arg1 => 1, arg2 => 'element of option', 'another_one' => ..., 'arg3', 3)>.
-
-=cut
-
-sub Get {
- my ( $self, $name, $user ) = @_;
-
- 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;
- }
- $res = $OPTIONS{$name} unless defined $res;
- $res = $META{$name}->{'Default'} unless defined $res;
- return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' );
-}
-
-=head2 GetObfuscated
-
-the same as Get, except it returns Obfuscated value via Obfuscate sub
-
-=cut
-
-sub GetObfuscated {
- my $self = shift;
- my ( $name, $user ) = @_;
- my $obfuscate = $META{$name}->{Obfuscate};
-
- # we use two Get here is to simplify the logic of the return value
- # configs need obfuscation are supposed to be less, so won't be too heavy
-
- return $self->Get(@_) unless $obfuscate;
-
- my $res = $self->Get(@_);
- $res = $obfuscate->( $self, $res, $user );
- return $self->_ReturnValue( $res, $META{$name}->{'Type'} || 'SCALAR' );
-}
-
-=head2 Set
-
-Set option's value to new value. Takes name of the option and new value.
-Returns old value.
-
-The new value should be scalar, array or hash depending on type of the option.
-If the option is not defined in meta or the default RT config then it is of
-scalar type.
-
-=cut
-
-sub Set {
- my ( $self, $name ) = ( shift, shift );
-
- my $old = $OPTIONS{$name};
- my $type = $META{$name}->{'Type'} || 'SCALAR';
- if ( $type eq 'ARRAY' ) {
- $OPTIONS{$name} = [@_];
- { no warnings 'once'; no strict 'refs'; @{"RT::$name"} = (@_); }
- } elsif ( $type eq 'HASH' ) {
- $OPTIONS{$name} = {@_};
- { no warnings 'once'; no strict 'refs'; %{"RT::$name"} = (@_); }
- } else {
- $OPTIONS{$name} = shift;
- {no warnings 'once'; no strict 'refs'; ${"RT::$name"} = $OPTIONS{$name}; }
- }
- $META{$name}->{'Type'} = $type;
- return $self->_ReturnValue( $old, $type );
-}
-
-sub _ReturnValue {
- my ( $self, $res, $type ) = @_;
- return $res unless wantarray;
-
- if ( $type eq 'ARRAY' ) {
- return @{ $res || [] };
- } elsif ( $type eq 'HASH' ) {
- return %{ $res || {} };
- }
- return $res;
-}
-
-sub SetFromConfig {
- my $self = shift;
- my %args = (
- Option => undef,
- Value => [],
- Package => 'RT',
- File => '',
- Line => 0,
- SiteConfig => 1,
- Extension => 0,
- @_
- );
-
- unless ( $args{'File'} ) {
- ( $args{'Package'}, $args{'File'}, $args{'Line'} ) = caller(1);
- }
-
- my $opt = $args{'Option'};
-
- my $type;
- my $name = $self->__GetNameByRef($opt);
- if ($name) {
- $type = ref $opt;
- $name =~ s/.*:://;
- } else {
- $name = $$opt;
- $type = $META{$name}->{'Type'} || 'SCALAR';
- }
-
- # if option is already set we have to check where
- # it comes from and may be ignore it
- if ( exists $OPTIONS{$name} ) {
- if ( $type eq 'HASH' ) {
- $args{'Value'} = [
- @{ $args{'Value'} },
- @{ $args{'Value'} }%2? (undef) : (),
- $self->Get( $name ),
- ];
- } elsif ( $args{'SiteConfig'} && $args{'Extension'} ) {
- # if it's site config of an extension then it can only
- # override options that came from its main config
- if ( $args{'Extension'} ne $META{$name}->{'Source'}{'Extension'} ) {
- my %source = %{ $META{$name}->{'Source'} };
- warn
- "Change of config option '$name' at $args{'File'} line $args{'Line'} has been ignored."
- ." This option earlier has been set in $source{'File'} line $source{'Line'}."
- ." To overide this option use ". ($source{'Extension'}||'RT')
- ." site config."
- ;
- return 1;
- }
- } elsif ( !$args{'SiteConfig'} && $META{$name}->{'Source'}{'SiteConfig'} ) {
- # if it's core config then we can override any option that came from another
- # core config, but not site config
-
- my %source = %{ $META{$name}->{'Source'} };
- if ( $source{'Extension'} ne $args{'Extension'} ) {
- # as a site config is loaded earlier then its base config
- # then we warn only on different extensions, for example
- # RTIR's options is set in main site config
- warn
- "Change of config option '$name' at $args{'File'} line $args{'Line'} has been ignored."
- ." It may be ok, but we want you to be aware."
- ." This option has been set earlier in $source{'File'} line $source{'Line'}."
- ;
- }
-
- return 1;
- }
- }
-
- $META{$name}->{'Type'} = $type;
- foreach (qw(Package File Line SiteConfig Extension)) {
- $META{$name}->{'Source'}->{$_} = $args{$_};
- }
- $self->Set( $name, @{ $args{'Value'} } );
-
- 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
-
-
-=head2 Meta
-
-=cut
-
-sub Meta {
- return $META{ $_[1] };
-}
-
-sub Sections {
- my $self = shift;
- my %seen;
- my @sections = sort
- grep !$seen{$_}++,
- map $_->{'Section'} || 'General',
- values %META;
- return @sections;
-}
-
-sub Options {
- my $self = shift;
- my %args = ( Section => undef, Overridable => 1, Sorted => 1, @_ );
- my @res = keys %META;
-
- @res = grep( ( $META{$_}->{'Section'} || 'General' ) eq $args{'Section'},
- @res
- ) if defined $args{'Section'};
-
- if ( defined $args{'Overridable'} ) {
- @res
- = grep( ( $META{$_}->{'Overridable'} || 0 ) == $args{'Overridable'},
- @res );
- }
-
- if ( $args{'Sorted'} ) {
- @res = sort {
- ($META{$a}->{SortOrder}||9999) <=> ($META{$b}->{SortOrder}||9999)
- || $a cmp $b
- } @res;
- } else {
- @res = sort { $a cmp $b } @res;
- }
- return @res;
-}
-
-=head2 AddOption( Name => '', Section => '', ... )
-
-=cut
-
-sub AddOption {
- my $self = shift;
- my %args = (
- Name => undef,
- Section => undef,
- Overridable => 0,
- SortOrder => undef,
- Widget => '/Widgets/Form/String',
- WidgetArguments => {},
- @_
- );
-
- unless ( $args{Name} ) {
- $RT::Logger->error("Need Name to add a new config");
- return;
- }
-
- unless ( $args{Section} ) {
- $RT::Logger->error("Need Section to add a new config option");
- return;
- }
-
- $META{ delete $args{Name} } = \%args;
-}
-
-=head2 DeleteOption( Name => '' )
-
-=cut
-
-sub DeleteOption {
- my $self = shift;
- my %args = (
- Name => undef,
- @_
- );
- if ( $args{Name} ) {
- delete $META{$args{Name}};
- }
- else {
- $RT::Logger->error("Need Name to remove a config option");
- return;
- }
-}
-
-=head2 UpdateOption( Name => '' ), Section => '', ... )
-
-=cut
-
-sub UpdateOption {
- my $self = shift;
- my %args = (
- Name => undef,
- Section => undef,
- Overridable => undef,
- SortOrder => undef,
- Widget => undef,
- WidgetArguments => undef,
- @_
- );
-
- my $name = delete $args{Name};
-
- unless ( $name ) {
- $RT::Logger->error("Need Name to update a new config");
- return;
- }
-
- unless ( exists $META{$name} ) {
- $RT::Logger->error("Config $name doesn't exist");
- return;
- }
-
- for my $type ( keys %args ) {
- next unless defined $args{$type};
- $META{$name}{$type} = $args{$type};
- }
- return 1;
-}
-
-RT::Base->_ImportOverlays();
-
-1;