2002-08-29 13:25:54 +04:00
|
|
|
# -*- 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>
|
2004-07-21 02:41:22 +04:00
|
|
|
# Erik Stambaugh <erik@dasbistro.com>
|
2005-10-12 12:51:55 +04:00
|
|
|
# Frédéric Buclin <LpSolit@gmail.com>
|
2002-08-29 13:25:54 +04:00
|
|
|
|
|
|
|
package Bugzilla::Config;
|
|
|
|
|
|
|
|
use strict;
|
|
|
|
|
|
|
|
use base qw(Exporter);
|
2006-06-29 20:49:56 +04:00
|
|
|
use Bugzilla::Constants;
|
2006-08-01 02:15:55 +04:00
|
|
|
use Data::Dumper;
|
|
|
|
use File::Temp;
|
2003-11-22 06:50:42 +03:00
|
|
|
|
2002-08-29 13:25:54 +04:00
|
|
|
# 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 =
|
|
|
|
(
|
2006-08-01 02:15:55 +04:00
|
|
|
admin => [qw(update_params SetParam write_params)],
|
2002-08-29 13:25:54 +04:00
|
|
|
);
|
2006-09-19 02:16:44 +04:00
|
|
|
Exporter::export_ok_tags('admin');
|
2002-08-29 13:25:54 +04:00
|
|
|
|
|
|
|
use vars qw(@param_list);
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2002-08-29 13:25:54 +04:00
|
|
|
# INITIALISATION CODE
|
2006-06-29 20:49:56 +04:00
|
|
|
# Perl throws a warning if we use bz_locations() directly after do.
|
2006-07-14 01:55:43 +04:00
|
|
|
our %params;
|
2005-10-12 12:51:55 +04:00
|
|
|
# Load in the param definitions
|
2006-06-20 00:15:18 +04:00
|
|
|
sub _load_params {
|
2006-07-04 01:23:26 +04:00
|
|
|
foreach my $module (param_panels()) {
|
|
|
|
eval("require Bugzilla::Config::$module") || die $@;
|
2006-06-20 00:15:18 +04:00
|
|
|
my @new_param_list = "Bugzilla::Config::$module"->get_param_list();
|
|
|
|
foreach my $item (@new_param_list) {
|
|
|
|
$params{$item->{'name'}} = $item;
|
|
|
|
}
|
|
|
|
push(@param_list, @new_param_list);
|
2005-10-12 12:51:55 +04:00
|
|
|
}
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
# END INIT CODE
|
|
|
|
|
|
|
|
# Subroutines go here
|
|
|
|
|
2006-07-04 01:23:26 +04:00
|
|
|
sub param_panels {
|
|
|
|
my @param_panels;
|
|
|
|
my $libpath = bz_locations()->{'libpath'};
|
|
|
|
foreach my $item ((glob "$libpath/Bugzilla/Config/*.pm")) {
|
|
|
|
$item =~ m#/([^/]+)\.pm$#;
|
|
|
|
my $module = $1;
|
|
|
|
push(@param_panels, $module) unless $module eq 'Common';
|
|
|
|
}
|
|
|
|
return @param_panels;
|
|
|
|
}
|
|
|
|
|
2002-08-29 13:25:54 +04:00
|
|
|
sub SetParam {
|
|
|
|
my ($name, $value) = @_;
|
|
|
|
|
2006-06-20 00:15:18 +04:00
|
|
|
_load_params unless %params;
|
2002-08-29 13:25:54 +04:00
|
|
|
die "Unknown param $name" unless (exists $params{$name});
|
|
|
|
|
|
|
|
my $entry = $params{$name};
|
|
|
|
|
|
|
|
# sanity check the value
|
2002-11-19 10:19:34 +03:00
|
|
|
|
|
|
|
# XXX - This runs the checks. Which would be good, except that
|
2005-11-26 00:57:13 +03:00
|
|
|
# check_shadowdb creates the database as a side effect, and so the
|
|
|
|
# checker fails the second time around...
|
2002-11-19 10:19:34 +03:00
|
|
|
if ($name ne 'shadowdb' && exists $entry->{'checker'}) {
|
2002-08-29 13:25:54 +04:00
|
|
|
my $err = $entry->{'checker'}->($value, $entry);
|
|
|
|
die "Param $name is not valid: $err" unless $err eq '';
|
|
|
|
}
|
|
|
|
|
2006-05-22 22:11:53 +04:00
|
|
|
Bugzilla->params->{$name} = $value;
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
sub update_params {
|
|
|
|
my ($params) = @_;
|
2006-12-09 14:51:35 +03:00
|
|
|
my $answer = Bugzilla->installation_answers;
|
2006-08-01 02:15:55 +04:00
|
|
|
|
|
|
|
my $param = read_param_file();
|
|
|
|
|
|
|
|
# If we didn't return any param values, then this is a new installation.
|
|
|
|
my $new_install = !(keys %$param);
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
# --- UPDATE OLD PARAMS ---
|
2006-05-22 22:11:53 +04:00
|
|
|
|
2006-06-19 19:09:22 +04:00
|
|
|
# Old Bugzilla versions stored the version number in the params file
|
2002-08-29 13:25:54 +04:00
|
|
|
# We don't want it, so get rid of it
|
2006-05-22 22:11:53 +04:00
|
|
|
delete $param->{'version'};
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2004-08-28 12:58:14 +04:00
|
|
|
# Change from usebrowserinfo to defaultplatform/defaultopsys combo
|
2006-05-22 22:11:53 +04:00
|
|
|
if (exists $param->{'usebrowserinfo'}) {
|
|
|
|
if (!$param->{'usebrowserinfo'}) {
|
|
|
|
if (!exists $param->{'defaultplatform'}) {
|
|
|
|
$param->{'defaultplatform'} = 'Other';
|
2004-08-28 12:58:14 +04:00
|
|
|
}
|
2006-05-22 22:11:53 +04:00
|
|
|
if (!exists $param->{'defaultopsys'}) {
|
|
|
|
$param->{'defaultopsys'} = 'Other';
|
2004-08-28 12:58:14 +04:00
|
|
|
}
|
|
|
|
}
|
2006-05-22 22:11:53 +04:00
|
|
|
delete $param->{'usebrowserinfo'};
|
2004-08-28 12:58:14 +04:00
|
|
|
}
|
|
|
|
|
2002-08-29 13:25:54 +04:00
|
|
|
# Change from a boolean for quips to multi-state
|
2006-05-22 22:11:53 +04:00
|
|
|
if (exists $param->{'usequip'} && !exists $param->{'enablequips'}) {
|
|
|
|
$param->{'enablequips'} = $param->{'usequip'} ? 'on' : 'off';
|
|
|
|
delete $param->{'usequip'};
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
|
2002-11-24 22:56:34 +03:00
|
|
|
# Change from old product groups to controls for group_control_map
|
|
|
|
# 2002-10-14 bug 147275 bugreport@peshkin.net
|
2006-08-01 02:15:55 +04:00
|
|
|
if (exists $param->{'usebuggroups'} &&
|
|
|
|
!exists $param->{'makeproductgroups'})
|
|
|
|
{
|
2006-05-22 22:11:53 +04:00
|
|
|
$param->{'makeproductgroups'} = $param->{'usebuggroups'};
|
2002-11-24 22:56:34 +03:00
|
|
|
}
|
2006-05-22 22:11:53 +04:00
|
|
|
if (exists $param->{'usebuggroupsentry'}
|
|
|
|
&& !exists $param->{'useentrygroupdefault'}) {
|
|
|
|
$param->{'useentrygroupdefault'} = $param->{'usebuggroupsentry'};
|
2002-11-24 22:56:34 +03:00
|
|
|
}
|
|
|
|
|
2003-03-22 07:47:35 +03:00
|
|
|
# Modularise auth code
|
2006-05-22 22:11:53 +04:00
|
|
|
if (exists $param->{'useLDAP'} && !exists $param->{'loginmethod'}) {
|
|
|
|
$param->{'loginmethod'} = $param->{'useLDAP'} ? "LDAP" : "DB";
|
2003-03-22 07:47:35 +03:00
|
|
|
}
|
|
|
|
|
2004-07-21 02:41:22 +04:00
|
|
|
# set verify method to whatever loginmethod was
|
2006-08-01 02:15:55 +04:00
|
|
|
if (exists $param->{'loginmethod'}
|
|
|
|
&& !exists $param->{'user_verify_class'})
|
|
|
|
{
|
2006-05-22 22:11:53 +04:00
|
|
|
$param->{'user_verify_class'} = $param->{'loginmethod'};
|
|
|
|
delete $param->{'loginmethod'};
|
2004-07-21 02:41:22 +04:00
|
|
|
}
|
|
|
|
|
2005-03-10 19:21:35 +03:00
|
|
|
# Remove quip-display control from parameters
|
|
|
|
# and give it to users via User Settings (Bug 41972)
|
2006-05-22 22:11:53 +04:00
|
|
|
if ( exists $param->{'enablequips'}
|
|
|
|
&& !exists $param->{'quip_list_entry_control'})
|
2005-03-10 19:21:35 +03:00
|
|
|
{
|
|
|
|
my $new_value;
|
2006-05-22 22:11:53 +04:00
|
|
|
($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'};
|
2005-03-10 19:21:35 +03:00
|
|
|
}
|
|
|
|
|
2006-11-03 02:32:00 +03:00
|
|
|
# Old mail_delivery_method choices contained no uppercase characters
|
|
|
|
if (exists $param->{'mail_delivery_method'}
|
|
|
|
&& $param->{'mail_delivery_method'} !~ /[A-Z]/) {
|
|
|
|
my $method = $param->{'mail_delivery_method'};
|
|
|
|
my %translation = (
|
|
|
|
'sendmail' => 'Sendmail',
|
|
|
|
'smtp' => 'SMTP',
|
|
|
|
'qmail' => 'Qmail',
|
|
|
|
'testfile' => 'Test',
|
|
|
|
'none' => 'None');
|
|
|
|
$param->{'mail_delivery_method'} = $translation{$method};
|
|
|
|
}
|
|
|
|
|
2002-08-29 13:25:54 +04:00
|
|
|
# --- DEFAULTS FOR NEW PARAMS ---
|
|
|
|
|
2006-06-20 00:15:18 +04:00
|
|
|
_load_params unless %params;
|
2002-08-29 13:25:54 +04:00
|
|
|
foreach my $item (@param_list) {
|
|
|
|
my $name = $item->{'name'};
|
2006-08-01 02:15:55 +04:00
|
|
|
unless (exists $param->{$name}) {
|
|
|
|
print "New parameter: $name\n" unless $new_install;
|
|
|
|
$param->{$name} = $answer->{$name} || $item->{'default'};
|
|
|
|
}
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
$param->{'utf8'} = 1 if $new_install;
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
# --- REMOVE OLD PARAMS ---
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
my @oldparams;
|
|
|
|
# Remove any old params, put them in old-params.txt
|
2006-05-22 22:11:53 +04:00
|
|
|
foreach my $item (keys %$param) {
|
2002-08-29 13:25:54 +04:00
|
|
|
if (!grep($_ eq $item, map ($_->{'name'}, @param_list))) {
|
2006-08-01 02:15:55 +04:00
|
|
|
local $Data::Dumper::Terse = 1;
|
2002-08-29 13:25:54 +04:00
|
|
|
local $Data::Dumper::Indent = 0;
|
2006-05-22 22:11:53 +04:00
|
|
|
push (@oldparams, [$item, Data::Dumper->Dump([$param->{$item}])]);
|
|
|
|
delete $param->{$item};
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
if (@oldparams) {
|
|
|
|
my $op_file = new IO::File('old-params.txt', '>>', 0600)
|
|
|
|
|| die "old-params.txt: $!";
|
|
|
|
|
|
|
|
print "The following parameters are no longer used in Bugzilla,",
|
|
|
|
" and so have been\nmoved from your parameters file into",
|
|
|
|
" old-params.txt:\n";
|
|
|
|
|
|
|
|
foreach my $p (@oldparams) {
|
|
|
|
my ($item, $value) = @$p;
|
|
|
|
print $op_file "\n\n$item:\n$value\n";
|
|
|
|
print $item;
|
|
|
|
print ", " unless $item eq $oldparams[$#oldparams]->[0];
|
|
|
|
}
|
|
|
|
print "\n";
|
|
|
|
$op_file->close;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (ON_WINDOWS && !-e SENDMAIL_EXE
|
2006-11-03 02:32:00 +03:00
|
|
|
&& $param->{'mail_delivery_method'} eq 'Sendmail')
|
2006-08-01 02:15:55 +04:00
|
|
|
{
|
|
|
|
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";
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2006-11-03 02:32:00 +03:00
|
|
|
$param->{'mail_delivery_method'} = 'SMTP';
|
2006-08-01 02:15:55 +04:00
|
|
|
}
|
|
|
|
|
|
|
|
write_params($param);
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
sub write_params {
|
|
|
|
my ($param_data) = @_;
|
|
|
|
$param_data ||= Bugzilla->params;
|
|
|
|
|
|
|
|
my $datadir = bz_locations()->{'datadir'};
|
|
|
|
my $param_file = "$datadir/params";
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2002-10-16 14:49:56 +04:00
|
|
|
# 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;
|
2002-08-29 13:25:54 +04:00
|
|
|
|
|
|
|
my ($fh, $tmpname) = File::Temp::tempfile('params.XXXXX',
|
2003-11-22 06:50:42 +03:00
|
|
|
DIR => $datadir );
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
print $fh (Data::Dumper->Dump([$param_data], ['*param']))
|
2002-08-29 13:25:54 +04:00
|
|
|
|| die "Can't write param file: $!";
|
|
|
|
|
|
|
|
close $fh;
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
rename $tmpname, $param_file
|
|
|
|
|| die "Can't rename $tmpname to $param_file: $!";
|
|
|
|
|
|
|
|
ChmodDataFile($param_file, 0666);
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
# And now we have to reset the params cache so that Bugzilla will re-read
|
|
|
|
# them.
|
|
|
|
delete Bugzilla->request_cache->{params};
|
2002-08-29 13:25:54 +04:00
|
|
|
}
|
|
|
|
|
2005-06-09 13:32:25 +04:00
|
|
|
# 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.
|
2002-08-29 13:25:54 +04:00
|
|
|
# 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;
|
2006-06-29 20:49:56 +04:00
|
|
|
if ((stat(bz_locations()->{'datadir'}))[2] & 0002) {
|
2002-08-29 13:25:54 +04:00
|
|
|
$perm = 0777;
|
|
|
|
}
|
|
|
|
$perm = $perm & $mask;
|
|
|
|
chmod $perm,$file;
|
|
|
|
}
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
sub read_param_file {
|
|
|
|
my %params;
|
|
|
|
my $datadir = bz_locations()->{'datadir'};
|
|
|
|
if (-e "$datadir/params") {
|
|
|
|
# Note that checksetup.pl sets file permissions on '$datadir/params'
|
|
|
|
|
|
|
|
# Using Safe mode is _not_ a guarantee of safety if someone does
|
|
|
|
# manage to write to the file. However, it won't hurt...
|
|
|
|
# See bug 165144 for not needing to eval this at all
|
|
|
|
my $s = new Safe;
|
|
|
|
|
|
|
|
$s->rdo("$datadir/params");
|
|
|
|
die "Error reading $datadir/params: $!" if $!;
|
|
|
|
die "Error evaluating $datadir/params: $@" if $@;
|
|
|
|
|
|
|
|
# Now read the param back out from the sandbox
|
|
|
|
%params = %{$s->varglob('param')};
|
|
|
|
}
|
|
|
|
return \%params;
|
|
|
|
}
|
|
|
|
|
2002-10-16 14:49:56 +04:00
|
|
|
1;
|
|
|
|
|
|
|
|
__END__
|
|
|
|
|
|
|
|
=head1 NAME
|
|
|
|
|
|
|
|
Bugzilla::Config - Configuration parameters for Bugzilla
|
|
|
|
|
|
|
|
=head1 SYNOPSIS
|
|
|
|
|
|
|
|
# Administration functions
|
|
|
|
use Bugzilla::Config qw(:admin);
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
update_params();
|
2002-10-16 14:49:56 +04:00
|
|
|
SetParam($param, $value);
|
2006-08-01 02:15:55 +04:00
|
|
|
write_params();
|
2002-10-16 14:49:56 +04:00
|
|
|
|
|
|
|
=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.
|
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
=item C<update_params()>
|
2002-10-16 14:49:56 +04:00
|
|
|
|
|
|
|
Updates the parameters, by transitioning old params to new formats, setting
|
2006-08-01 02:15:55 +04:00
|
|
|
defaults for new params, and removing obsolete ones. Used by F<checksetup.pl>
|
|
|
|
in the process of an installation or upgrade.
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Prints out information about what it's doing, if it makes any changes.
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
May prompt the user for input, if certain required parameters are not
|
|
|
|
specified.
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
=item C<write_params($params)>
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Description: Writes the parameters to disk.
|
2002-08-29 13:25:54 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Params: C<$params> (optional) - A hashref to write to the disk
|
|
|
|
instead of C<Bugzilla->params>. Used only by
|
|
|
|
C<update_params>.
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Returns: nothing
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
=item C<read_param_file()>
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Description: Most callers should never need this. This is used
|
|
|
|
by C<Bugzilla->params> to directly read C<$datadir/params>
|
|
|
|
and load it into memory. Use C<Bugzilla->params> instead.
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Params: none
|
2002-10-16 14:49:56 +04:00
|
|
|
|
2006-08-01 02:15:55 +04:00
|
|
|
Returns: A hashref containing the current params in C<$datadir/params>.
|
2002-10-16 14:49:56 +04:00
|
|
|
|
|
|
|
=back
|