Config.pm 13.2 KB
Newer Older
1 2 3
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
4
#
5 6
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.
7 8 9

package Bugzilla::Config;

10
use 5.10.1;
11
use strict;
12
use warnings;
13

14
use parent qw(Exporter);
15
use autodie qw(:default);
16

17
use Bugzilla::Constants;
18
use Bugzilla::Hook;
19 20 21 22
use Bugzilla::Util qw(trick_taint);

use JSON::XS;
use File::Slurp;
23
use File::Temp;
24
use File::Basename;
25

26 27 28 29 30
# 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
%Bugzilla::Config::EXPORT_TAGS =
  (
31
   admin => [qw(update_params SetParam write_params)],
32
  );
33
Exporter::export_ok_tags('admin');
34 35

# INITIALISATION CODE
36
# Perl throws a warning if we use bz_locations() directly after do.
37
our %params;
38
# Load in the param definitions
39
sub _load_params {
40
    my $panels = param_panels();
41
    my %hook_panels;
42 43 44
    foreach my $panel (keys %$panels) {
        my $module = $panels->{$panel};
        eval("require $module") || die $@;
45 46
        my @new_param_list = $module->get_param_list();
        $hook_panels{lc($panel)} = { params => \@new_param_list };
47
    }
48 49
    # This hook is also called in editparams.cgi. This call here is required
    # to make SetParam work.
50
    Bugzilla::Hook::process('config_modify_panels', 
51
                            { panels => \%hook_panels });
52 53 54 55 56 57

    foreach my $panel (keys %hook_panels) {
        foreach my $item (@{$hook_panels{$panel}->{params}}) {
            $params{$item->{'name'}} = $item;
        }
    }
58 59 60 61 62
}
# END INIT CODE

# Subroutines go here

63
sub param_panels {
64
    my $param_panels = {};
65 66 67 68
    my $libpath = bz_locations()->{'libpath'};
    foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
        $item =~ m#/([^/]+)\.pm$#;
        my $module = $1;
69
        $param_panels->{$module} = "Bugzilla::Config::$module" unless $module eq 'Common';
70
    }
71
    # Now check for any hooked params
72
    Bugzilla::Hook::process('config_add_panels', 
73
                            { panel_modules => $param_panels });
74
    return $param_panels;
75 76
}

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

80
    _load_params unless %params;
81 82 83 84 85
    die "Unknown param $name" unless (exists $params{$name});

    my $entry = $params{$name};

    # sanity check the value
86 87

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

95
    Bugzilla->params->{$name} = $value;
96 97
}

98 99
sub update_params {
    my ($params) = @_;
100
    my $answer = Bugzilla->installation_answers;
101 102 103 104 105
    my $datadir = bz_locations()->{'datadir'};
    my $param;

    # If the old data/params file using Data::Dumper output still exists,
    # read it. It will be deleted once the parameters are stored in the new
106
    # data/params.json file.
107 108 109 110 111 112 113 114 115 116 117 118 119 120
    my $old_file = "$datadir/params";

    if (-e $old_file) {
        require Safe;
        my $s = new Safe;

        $s->rdo($old_file);
        die "Error reading $old_file: $!" if $!;
        die "Error evaluating $old_file: $@" if $@;

        # Now read the param back out from the sandbox.
        $param = \%{ $s->varglob('param') };
    }
    else {
121 122 123 124 125 126
        # Rename params.js to params.json if checksetup.pl
        # was executed with an earlier version of this change
        rename "$old_file.js", "$old_file.json"
            if -e "$old_file.js" && !-e "$old_file.json";

        # Read the new data/params.json file.
127 128
        $param = read_param_file();
    }
129

130
    my %new_params;
131 132 133

    # If we didn't return any param values, then this is a new installation.
    my $new_install = !(keys %$param);
134

135
    # --- UPDATE OLD PARAMS ---
136

137
    # Change from usebrowserinfo to defaultplatform/defaultopsys combo
138 139 140
    if (exists $param->{'usebrowserinfo'}) {
        if (!$param->{'usebrowserinfo'}) {
            if (!exists $param->{'defaultplatform'}) {
141
                $new_params{'defaultplatform'} = 'Other';
142
            }
143
            if (!exists $param->{'defaultopsys'}) {
144
                $new_params{'defaultopsys'} = 'Other';
145 146 147 148
            }
        }
    }

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

154 155
    # Change from old product groups to controls for group_control_map
    # 2002-10-14 bug 147275 bugreport@peshkin.net
156 157 158
    if (exists $param->{'usebuggroups'} && 
        !exists $param->{'makeproductgroups'}) 
    {
159
        $new_params{'makeproductgroups'} = $param->{'usebuggroups'};
160 161
    }

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

167
    # set verify method to whatever loginmethod was
168 169 170
    if (exists $param->{'loginmethod'} 
        && !exists $param->{'user_verify_class'}) 
    {
171
        $new_params{'user_verify_class'} = $param->{'loginmethod'};
172 173
    }

174 175
    # Remove quip-display control from parameters
    # and give it to users via User Settings (Bug 41972)
176 177
    if ( exists $param->{'enablequips'} 
         && !exists $param->{'quip_list_entry_control'}) 
178 179
    {
        my $new_value;
180 181 182 183
        ($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';};
184
        $new_params{'quip_list_entry_control'} = $new_value;
185 186
    }

187
    # Old mail_delivery_method choices contained no uppercase characters
188 189 190 191 192 193 194 195 196 197 198 199 200
    my $mta = $param->{'mail_delivery_method'};
    if ($mta) {
        if ($mta !~ /[A-Z]/) {
            my %translation = (
                'sendmail' => 'Sendmail',
                'smtp'     => 'SMTP',
                'qmail'    => 'Qmail',
                'testfile' => 'Test',
                'none'     => 'None');
            $param->{'mail_delivery_method'} = $translation{$mta};
        }
        # This will force the parameter to be reset to its default value.
        delete $param->{'mail_delivery_method'} if $param->{'mail_delivery_method'} eq 'Qmail';
201 202
    }

203 204 205
    # Convert the old "ssl" parameter to the new "ssl_redirect" parameter.
    # Both "authenticated sessions" and "always" turn on "ssl_redirect"
    # when upgrading.
206
    if (exists $param->{'ssl'} and $param->{'ssl'} ne 'never') {
207
        $new_params{'ssl_redirect'} = 1;
208
    }
209

210 211 212 213 214
    # "specific_search_allow_empty_words" has been renamed to "search_allow_no_criteria".
    if (exists $param->{'specific_search_allow_empty_words'}) {
        $new_params{'search_allow_no_criteria'} = $param->{'specific_search_allow_empty_words'};
    }

215 216
    # --- DEFAULTS FOR NEW PARAMS ---

217
    _load_params unless %params;
218 219
    foreach my $name (keys %params) {
        my $item = $params{$name};
220 221
        unless (exists $param->{$name}) {
            print "New parameter: $name\n" unless $new_install;
222 223 224 225
            if (exists $new_params{$name}) {
                $param->{$name} = $new_params{$name};
            }
            elsif (exists $answer->{$name}) {
226 227 228 229 230
                $param->{$name} = $answer->{$name};
            }
            else {
                $param->{$name} = $item->{'default'};
            }
231
        }
232 233
    }

234
    $param->{'utf8'} = 1 if $new_install;
235

236 237 238
    # Bug 452525: OR based groups are on by default for new installations
    $param->{'or_groups'} = 1 if $new_install;

239
    # --- REMOVE OLD PARAMS ---
240

241
    my %oldparams;
242
    # Remove any old params
243
    foreach my $item (keys %$param) {
244 245
        if (!exists $params{$item}) {
            $oldparams{$item} = delete $param->{$item};
246 247 248
        }
    }

249 250
    # Write any old parameters to old-params.txt
    my $old_param_file = "$datadir/old-params.txt";
251
    if (scalar(keys %oldparams)) {
252 253
        my $op_file = new IO::File($old_param_file, '>>', 0600)
          || die "Couldn't create $old_param_file: $!";
254 255 256

        print "The following parameters are no longer used in Bugzilla,",
              " and so have been\nmoved from your parameters file into",
257
              " $old_param_file:\n";
258

259 260
        my $comma = "";
        foreach my $item (keys %oldparams) {
261
            print $op_file "\n\n$item:\n" . $oldparams{$item} . "\n";
262 263
            print "${comma}$item";
            $comma = ", ";
264 265 266 267 268 269
        }
        print "\n";
        $op_file->close;
    }

    if (ON_WINDOWS && !-e SENDMAIL_EXE
270
        && $param->{'mail_delivery_method'} eq 'Sendmail')
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
    {
        my $smtp = $answer->{'SMTP_SERVER'};
        if (!$smtp) {
            print "\nBugzilla requires an SMTP server to function on",
                  " Windows.\nPlease enter your SMTP server's hostname: ";
            $smtp = <STDIN>;
            chomp $smtp;
            if ($smtp) {
                $param->{'smtpserver'} = $smtp;
             }
             else {
                print "\nWarning: No SMTP Server provided, defaulting to",
                      " localhost\n";
            }
        }

287
        $param->{'mail_delivery_method'} = 'SMTP';
288 289 290
    }

    write_params($param);
291

292 293
    if (-e $old_file) {
        unlink $old_file;
294
        say "$old_file has been converted into $old_file.json, using the JSON format.";
295 296
    }

297 298 299
    # Return deleted params and values so that checksetup.pl has a chance
    # to convert old params to new data.
    return %oldparams;
300 301
}

302 303 304
sub write_params {
    my ($param_data) = @_;
    $param_data ||= Bugzilla->params;
305
    my $param_file = bz_locations()->{'datadir'} . '/params.json';
306

307 308
    my $json_data = JSON::XS->new->canonical->pretty->encode($param_data);
    write_file($param_file, { binmode => ':utf8', atomic => 1 }, \$json_data);
309

310 311 312 313
    # It's not common to edit parameters and loading
    # Bugzilla::Install::Filesystem is slow.
    require Bugzilla::Install::Filesystem;
    Bugzilla::Install::Filesystem::fix_file_permissions($param_file);
314

315 316 317
    # And now we have to reset the params cache so that Bugzilla will re-read
    # them.
    delete Bugzilla->request_cache->{params};
318 319
}

320 321
sub read_param_file {
    my %params;
322
    my $file = bz_locations()->{'datadir'} . '/params.json';
323 324 325 326 327

    if (-e $file) {
        my $data;
        read_file($file, binmode => ':utf8', buf_ref => \$data);

328
        # If params.json has been manually edited and e.g. some quotes are
329 330 331 332 333 334 335 336 337 338
        # missing, we don't want JSON::XS to leak the content of the file
        # to all users in its error message, so we have to eval'uate it.
        %params = eval { %{JSON::XS->new->decode($data)} };
        if ($@) {
            my $error_msg = (basename($0) eq 'checksetup.pl') ?
                $@ : 'run checksetup.pl to see the details.';
            die "Error parsing $file: $error_msg";
        }
        # JSON::XS doesn't detaint data for us.
        trick_taint($params{$_}) foreach keys %params;
339
    }
340 341 342 343 344 345 346 347
    elsif ($ENV{'SERVER_SOFTWARE'}) {
       # We're in a CGI, but the params file doesn't exist. We can't
       # Template Toolkit, or even install_string, since checksetup
       # might not have thrown an error. Bugzilla::CGI->new
       # hasn't even been called yet, so we manually use CGI::Carp here
       # so that the user sees the error.
       require CGI::Carp;
       CGI::Carp->import('fatalsToBrowser');
348
       die "The $file file does not exist."
349 350
           . ' You probably need to run checksetup.pl.',
    }
351 352 353
    return \%params;
}

354 355 356 357 358 359 360 361 362 363 364 365 366
1;

__END__

=head1 NAME

Bugzilla::Config - Configuration parameters for Bugzilla

=head1 SYNOPSIS

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

367
  update_params();
368
  SetParam($param, $value);
369
  write_params();
370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387

=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<SetParam($name, $value)>

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

388
=item C<update_params()>
389 390

Updates the parameters, by transitioning old params to new formats, setting
391 392
defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
in the process of an installation or upgrade.
393

394
Prints out information about what it's doing, if it makes any changes.
395

396 397
May prompt the user for input, if certain required parameters are not
specified.
398

399
=item C<write_params($params)>
400

401
Description: Writes the parameters to disk.
402

403
Params:      C<$params> (optional) - A hashref to write to the disk
404
               instead of C<Bugzilla-E<gt>params>. Used only by
405
               C<update_params>.
406

407
Returns:     nothing
408

409
=item C<read_param_file()>
410

411
Description: Most callers should never need this. This is used
412
             by C<Bugzilla-E<gt>params> to directly read C<$datadir/params.json>
413
             and load it into memory. Use C<Bugzilla-E<gt>params> instead.
414

415
Params:      none
416

417
Returns:     A hashref containing the current params in C<$datadir/params.json>.
418

419 420
=item C<param_panels()>

421
=back