# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
#                 Dawn Endico <endico@mozilla.org>
#                 Dan Mosedale <dmose@mozilla.org>
#                 Joe Robins <jmrobins@tgix.com>
#                 Jake <jake@bugzilla.org>
#                 J. Paul Reed <preed@sigkill.com>
#                 Bradley Baetz <bbaetz@student.usyd.edu.au>
#                 Christopher Aillon <christopher@aillon.com>
#                 Erik Stambaugh <erik@dasbistro.com>
#                 Frédéric Buclin <LpSolit@gmail.com>

package Bugzilla::Config;

use strict;

use base qw(Exporter);

# Under mod_perl, get this from a .htaccess config variable,
# and/or default from the current 'real' dir
# At some stage after this, it may be possible for these dir locations
# to go into localconfig. localconfig can't be specified in a config file,
# except possibly with mod_perl. If you move localconfig, you need to change
# the define here.
# $libpath is really only for mod_perl; its not yet possible to move the
# .pms elsewhere.
# $webdotdir must be in the webtree somewhere. Even if you use a local dot,
# we output images to there. Also, if $webdot dir is not relative to the
# bugzilla root directory, you'll need to change showdependencygraph.cgi to
# set image_url to the correct location.
# The script should really generate these graphs directly...
# Note that if $libpath is changed, some stuff will break, notably dependency
# graphs (since the path will be wrong in the HTML). This will be fixed at
# some point.

# constant paths
our $libpath = '.';
our $templatedir = "$libpath/template";

# variable paths
our $project;
our $localconfig;
our $datadir;
if ($ENV{'PROJECT'} && $ENV{'PROJECT'} =~ /^(\w+)$/) {
    $project = $1;
    $localconfig = "$libpath/localconfig.$project";
    $datadir = "$libpath/data/$project";
} else {
    $localconfig = "$libpath/localconfig";
    $datadir = "$libpath/data";
}
our $attachdir = "$datadir/attachments";
our $webdotdir = "$datadir/webdot";
our $extensionsdir = "$libpath/extensions";

our @parampanels = ();

# Module stuff
@Bugzilla::Config::EXPORT = qw(Param);

# Don't export localvars by default - people should have to explicitly
# ask for it, as a (probably futile) attempt to stop code using it
# when it shouldn't
# ChmodDataFile is here until that stuff all moves out of globals.pl
# into this file
@Bugzilla::Config::EXPORT_OK = qw(ChmodDataFile);

%Bugzilla::Config::EXPORT_TAGS =
  (
   admin => [qw(UpdateParams SetParam WriteParams)],
   db => [qw($db_driver $db_host $db_port $db_name $db_user $db_pass $db_sock)],
   locations => [qw($libpath $localconfig $attachdir $datadir $templatedir
                    $webdotdir $project $extensionsdir)],
   params => [qw(@parampanels)],
  );
Exporter::export_ok_tags('admin', 'db', 'locations', 'params');

# Bugzilla version
$Bugzilla::Config::VERSION = "2.23.1+";

use vars qw(@param_list);

# Data::Dumper is required as needed, below. The problem is that then when
# the code locally sets $Data::Dumper::Foo, this triggers 'used only once'
# warnings.
# We can't predeclare another package's vars, though, so just use them
{
    local $Data::Dumper::Sortkeys;
    local $Data::Dumper::Terse;
    local $Data::Dumper::Indent;
}

# INITIALISATION CODE
do $localconfig;
my %params;
# Load in the param definitions
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
    $item =~ m#/([^/]+)\.pm$#;
    my $module = $1;
    next if ($module eq 'Common');
    require "Bugzilla/Config/$module.pm";
    my @new_param_list = "Bugzilla::Config::$module"->get_param_list();
    foreach my $item (@new_param_list) {
        $params{$item->{'name'}} = $item;
    }
    push(@parampanels, $module);
    push(@param_list, @new_param_list);
}

# END INIT CODE

# Subroutines go here

sub Param {
    my ($param) = @_;

    my %param_values = %{Bugzilla->params};

    # By this stage, the param must be in the hash
    die "Can't find param named $param" unless (exists $params{$param});

    # When module startup code runs (which is does even via -c, when using
    # |use|), we may try to grab params which don't exist yet. This affects
    # tests, so have this as a fallback for the -c case
    return $params{$param}->{default} 
        if ($^C && not exists $param_values{$param});

    # If we have a value for the param, return it
    return $param_values{$param} if exists $param_values{$param};

    # Else error out
    die "No value for param $param (try running checksetup.pl again)";
}

sub SetParam {
    my ($name, $value) = @_;

    die "Unknown param $name" unless (exists $params{$name});

    my $entry = $params{$name};

    # sanity check the value

    # XXX - This runs the checks. Which would be good, except that
    # check_shadowdb creates the database as a side effect, and so the
    # checker fails the second time around...
    if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
        my $err = $entry->{'checker'}->($value, $entry);
        die "Param $name is not valid: $err" unless $err eq '';
    }

    Bugzilla->params->{$name} = $value;
}

sub UpdateParams {
    # --- PARAM CONVERSION CODE ---

    # Note that this isn't particuarly 'clean' in terms of separating
    # the backend code (ie this) from the actual params.
    # We don't care about that, though

    my $param = Bugzilla->params;

    # Old bugzilla versions stored the version number in the params file
    # We don't want it, so get rid of it
    delete $param->{'version'};

    # Change from usebrowserinfo to defaultplatform/defaultopsys combo
    if (exists $param->{'usebrowserinfo'}) {
        if (!$param->{'usebrowserinfo'}) {
            if (!exists $param->{'defaultplatform'}) {
                $param->{'defaultplatform'} = 'Other';
            }
            if (!exists $param->{'defaultopsys'}) {
                $param->{'defaultopsys'} = 'Other';
            }
        }
        delete $param->{'usebrowserinfo'};
    }

    # Change from a boolean for quips to multi-state
    if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
        $param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
        delete $param->{'usequip'};
    }

    # Change from old product groups to controls for group_control_map
    # 2002-10-14 bug 147275 bugreport@peshkin.net
    if (exists $param->{'usebuggroups'} && !exists $param->{'makeproductgroups'}) {
        $param->{'makeproductgroups'} = $param->{'usebuggroups'};
    }
    if (exists $param->{'usebuggroupsentry'} 
       && !exists $param->{'useentrygroupdefault'}) {
        $param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
    }

    # Modularise auth code
    if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
        $param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
    }

    # set verify method to whatever loginmethod was
    if (exists $param->{'loginmethod'} && !exists $param->{'user_verify_class'}) {
        $param->{'user_verify_class'} = $param->{'loginmethod'};
        delete $param->{'loginmethod'};
    }

    # Remove quip-display control from parameters
    # and give it to users via User Settings (Bug 41972)
    if ( exists $param->{'enablequips'} 
         && !exists $param->{'quip_list_entry_control'}) 
    {
        my $new_value;
        ($param->{'enablequips'} eq 'on')       && do {$new_value = 'open';};
        ($param->{'enablequips'} eq 'approved') && do {$new_value = 'moderated';};
        ($param->{'enablequips'} eq 'frozen')   && do {$new_value = 'closed';};
        ($param->{'enablequips'} eq 'off')      && do {$new_value = 'closed';};
        $param->{'quip_list_entry_control'} = $new_value;
        delete $param->{'enablequips'};
    }

    # --- DEFAULTS FOR NEW PARAMS ---

    foreach my $item (@param_list) {
        my $name = $item->{'name'};
        $param->{$name} = $item->{'default'} unless exists $param->{$name};
    }

    # --- REMOVE OLD PARAMS ---

    my @oldparams = ();

    # Remove any old params
    foreach my $item (keys %$param) {
        if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
            require Data::Dumper;

            local $Data::Dumper::Terse = 1;
            local $Data::Dumper::Indent = 0;
            push (@oldparams, [$item, Data::Dumper->Dump([$param->{$item}])]);
            delete $param->{$item};
        }
    }

    return @oldparams;
}

sub WriteParams {
    require Data::Dumper;

    # This only has an affect for Data::Dumper >= 2.12 (ie perl >= 5.8.0)
    # Its just cosmetic, though, so that doesn't matter
    local $Data::Dumper::Sortkeys = 1;

    require File::Temp;
    my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
                                              DIR => $datadir );

    print $fh (Data::Dumper->Dump([Bugzilla->params], ['*param']))
      || die "Can't write param file: $!";

    close $fh;

    rename $tmpname, "$datadir/params"
      || die "Can't rename $tmpname to $datadir/params: $!";

    ChmodDataFile("$datadir/params", 0666);
}

# Some files in the data directory must be world readable if and only if
# we don't have a webserver group. Call this function to do this.
# This will become a private function once all the datafile handling stuff
# moves into this package

# This sub is not perldoc'd for that reason - noone should know about it
sub ChmodDataFile {
    my ($file, $mask) = @_;
    my $perm = 0770;
    if ((stat($datadir))[2] & 0002) {
        $perm = 0777;
    }
    $perm = $perm & $mask;
    chmod $perm,$file;
}

1;

__END__

=head1 NAME

Bugzilla::Config - Configuration parameters for Bugzilla

=head1 SYNOPSIS

  # Getting parameters
  use Bugzilla::Config;

  my $fooSetting = Param('foo');

  # Administration functions
  use Bugzilla::Config qw(:admin);

  my @removed_params = UpgradeParams();
  SetParam($param, $value);
  WriteParams();

  # Localconfig variables may also be imported
  use Bugzilla::Config qw(:db);
  print "Connecting to $db_name as $db_user with $db_pass\n";

=head1 DESCRIPTION

This package contains ways to access Bugzilla configuration parameters.

=head1 FUNCTIONS

=head2 Parameters

Parameters can be set, retrieved, and updated.

=over 4

=item C<Param($name)>

Returns the Param with the specified name. Either a string, or, in the case
of multiple-choice parameters, an array reference.

=item C<SetParam($name, $value)>

Sets the param named $name to $value. Values are checked using the checker
function for the given param if one exists.

=item C<UpdateParams()>

Updates the parameters, by transitioning old params to new formats, setting
defaults for new params, and removing obsolete ones.

Any removed params are returned in a list, with elements [$item, $oldvalue]
where $item is the entry in the param list.

=item C<WriteParams()>

Writes the parameters to disk.

=back

=over

=item *

The new value for the parameter

=item *

A reference to the entry in the param list for this parameter

Functions should return error text, or the empty string if there was no error.

=back