#!/usr/bonsaitools/bin/perl -wT
# -*- 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.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>
#                 Dan Mosedale <dmose@mozilla.org>
#                 Alan Raetz <al_raetz@yahoo.com>
#                 David Miller <justdave@syndicomm.com>
#                 Christopher Aillon <christopher@aillon.com>
#                 Gervase Markham <gerv@gerv.net>

use diagnostics;
use strict;

use lib qw(.);

require "CGI.pl";

use RelationSet;

# Shut up misguided -w warnings about "used only once". "use vars" just
# doesn't work for me.
sub sillyness {
    my $zz;
    $zz = $::defaultqueryname;
    $zz = $::usergroupset;
}

# Use global template variables.
use vars qw($template $vars $userid);

# The default email flags leave all email on.
my $defaultflagstring = "ExcludeSelf~on~";

my @roles = ("Owner", "Reporter", "QAcontact", "CClist", "Voter");
my @reasons = ("Removeme", "Comments", "Attachments", "Status", "Resolved", 
               "Keywords", "CC", "Other");

foreach my $role (@roles) {
    foreach my $reason (@reasons) {
        $defaultflagstring .= "email$role$reason~on~";
    }
}

# Remove final "~".
chop $defaultflagstring;

###############################################################################
# Each panel has two functions - panel Foo has a DoFoo, to get the data 
# necessary for displaying the panel, and a SaveFoo, to save the panel's 
# contents from the form data (if appropriate.) 
# SaveFoo may be called before DoFoo.    
###############################################################################
sub DoAccount {
    SendSQL("SELECT realname FROM profiles WHERE userid = $userid");
    $vars->{'realname'} = FetchSQLData();

    if(Param('allowemailchange')) {
        SendSQL("SELECT tokentype, DATE_ADD(issuedate,INTERVAL 3 DAY),
                    eventdata 
                    FROM tokens
                    WHERE userid = $userid
                    AND tokentype LIKE 'email%' 
                    ORDER BY tokentype ASC LIMIT 1");
        if(MoreSQLData()) {
            my ($tokentype, $change_date, $eventdata) = &::FetchSQLData();
            $vars->{'login_change_date'} = $change_date;

            if($tokentype eq 'emailnew') {
                my ($oldemail,$newemail) = split(/:/,$eventdata);
                $vars->{'new_login_name'} = $newemail;
            }
        }
    }
}

sub SaveAccount {
    my $pwd1 = $::FORM{'new_password1'};
    my $pwd2 = $::FORM{'new_password2'};

    if ($::FORM{'Bugzilla_password'} ne "" || 
        $pwd1 ne "" || $pwd2 ne "") 
    {
        my $old = SqlQuote($::FORM{'Bugzilla_password'});
        SendSQL("SELECT cryptpassword FROM profiles WHERE userid = $userid");
        my $oldcryptedpwd = FetchOneColumn();
        if (!$oldcryptedpwd) {
            DisplayError("I was unable to retrieve your old password from the database.");
            exit;
        }
        if (crypt($::FORM{'Bugzilla_password'}, $oldcryptedpwd) ne 
                  $oldcryptedpwd) 
        {
            DisplayError("You did not enter your old password correctly.");
            exit;
        }

        if ($pwd1 ne "" || $pwd2 ne "")
        {
            if ($::FORM{'new_password1'} eq '') {
                DisplayError("You must enter a new password.");
                exit;
            }
            my $passworderror = ValidatePassword($pwd1, $pwd2);
            (DisplayError($passworderror) && exit) if $passworderror;
        
            my $cryptedpassword = SqlQuote(Crypt($pwd1));
            SendSQL("UPDATE profiles 
                     SET    cryptpassword = $cryptedpassword 
                     WHERE  userid = $userid");
            # Invalidate all logins except for the current one
            InvalidateLogins($userid, $::COOKIE{"Bugzilla_logincookie"});
        }
    }

    if(Param("allowemailchange") && $::FORM{'new_login_name'}) {
        my $old_login_name = $::FORM{'Bugzilla_login'};
        my $new_login_name = trim($::FORM{'new_login_name'});

        if($old_login_name ne $new_login_name) {
            if( $::FORM{'Bugzilla_password'} eq "") {
                DisplayError("You must enter your old password to 
                                             change email address.");
                exit;
            }

            use Token;
            # Block multiple email changes for the same user.
            if (Token::HasEmailChangeToken($userid)) {
                DisplayError("Email change already in progress; 
                                          please check your email.");
                exit;
            }

            # Before changing an email address, confirm one does not exist.
            CheckEmailSyntax($new_login_name);
            trick_taint($new_login_name);
            if (!ValidateNewUser($new_login_name)) {
                DisplayError("Account $new_login_name already exists");
                exit;
            }

            Token::IssueEmailChangeToken($userid,$old_login_name,
                                                 $new_login_name);

            $vars->{'changes_saved'} = 
                "An email has been sent to both old and new email 
                 addresses to confirm the change of email address.";
        }
    }

    SendSQL("UPDATE profiles SET " .
            "realname = " . SqlQuote(trim($::FORM{'realname'})) .
            " WHERE userid = $userid");
}


sub DoEmail {
    if (Param("supportwatchers")) {
        my $watcheduserSet = new RelationSet;
        $watcheduserSet->mergeFromDB("SELECT watched FROM watch WHERE" .
                                    " watcher=$userid");
        $vars->{'watchedusers'} = $watcheduserSet->toString();
    }

    SendSQL("SELECT emailflags FROM profiles WHERE userid = $userid");

    my ($flagstring) = FetchSQLData();

    # If the emailflags haven't been set before, that means that this user 
    # hasn't been to the email pane of userprefs.cgi since the change to 
    # use emailflags. Create a default flagset for them, based on
    # static defaults. 
    if (!$flagstring) {
        $flagstring = $defaultflagstring;
        SendSQL("UPDATE profiles SET emailflags = " .
                SqlQuote($flagstring) . " WHERE userid = $userid");
    }

    # The 255 param is here, because without a third param, split will
    # trim any trailing null fields, which causes Perl to eject lots of
    # warnings. Any suitably large number would do.
    my %emailflags = split(/~/, $flagstring, 255);

    # Determine the value of the "excludeself" global email preference.
    # Note that the value of "excludeself" is assumed to be off if the
    # preference does not exist in the user's list, unlike other 
    # preferences whose value is assumed to be on if they do not exist.
    if (exists($emailflags{'ExcludeSelf'}) 
        && $emailflags{'ExcludeSelf'} eq 'on')
    {
        $vars->{'excludeself'} = 1;
    }
    else {
        $vars->{'excludeself'} = 0;
    }
    
    # Parse the info into a hash of hashes; the first hash keyed by role,
    # the second by reason, and the value being 1 or 0 for (on or off).
    # Preferences not existing in the user's list are assumed to be on.
    foreach my $role (@roles) {
        $vars->{$role} = {};
        foreach my $reason (@reasons) {
            my $key = "email$role$reason";
            if (!exists($emailflags{$key}) || $emailflags{$key} eq 'on') {
                $vars->{$role}{$reason} = 1;
            }
            else {
                $vars->{$role}{$reason} = 0;
            }
        }
    }
}

# Note: we no longer store "off" values in the database.
sub SaveEmail {
    my $updateString = "";
    
    if (defined $::FORM{'ExcludeSelf'}) {
        $updateString .= 'ExcludeSelf~on';
    } else {
        $updateString .= 'ExcludeSelf~';
    }
    
    foreach my $role (@roles) {
        foreach my $reason (@reasons) {
            # Add this preference to the list without giving it a value,
            # which is the equivalent of setting the value to "off."
            $updateString .= "~email$role$reason~";
            
            # If the form field for this preference is defined, then we
            # know the checkbox was checked, so set the value to "on".
            $updateString .= "on" if defined $::FORM{"email$role$reason"};
        }
    }
            
    SendSQL("UPDATE profiles SET emailflags = " . SqlQuote($updateString) . 
            " WHERE userid = $userid");

    if (Param("supportwatchers") && exists $::FORM{'watchedusers'}) {
        # Just in case.  Note that this much locking is actually overkill:
        # we don't really care if anyone reads the watch table.  So 
        # some small amount of contention could be gotten rid of by
        # using user-defined locks rather than table locking.
        SendSQL("LOCK TABLES watch WRITE, profiles READ");

        # what the db looks like now
        my $origWatchedUsers = new RelationSet;
        $origWatchedUsers->mergeFromDB("SELECT watched FROM watch WHERE" .
                                       " watcher=$userid");

        # Update the database to look like the form
        my $newWatchedUsers = new RelationSet($::FORM{'watchedusers'});
        my @CCDELTAS = $origWatchedUsers->generateSqlDeltas(
                                                         $newWatchedUsers, 
                                                         "watch", 
                                                         "watcher", 
                                                         $userid,
                                                         "watched");
        ($CCDELTAS[0] eq "") || SendSQL($CCDELTAS[0]);
        ($CCDELTAS[1] eq "") || SendSQL($CCDELTAS[1]);

        SendSQL("UNLOCK TABLES");       
    }
}


sub DoFooter {
    SendSQL("SELECT mybugslink FROM profiles " .
            "WHERE userid = $userid");
    $vars->{'mybugslink'} = FetchSQLData();
    
    SendSQL("SELECT name, linkinfooter FROM namedqueries " .
            "WHERE userid = $userid");
    
    my @queries;        
    while (MoreSQLData()) {
        my ($name, $footer) = (FetchSQLData());
        next if ($name eq $::defaultqueryname);
        
        push (@queries, { name => $name, footer => $footer });        
    }
    
    $vars->{'queries'} = \@queries;
}
              
sub SaveFooter {
    my %old;
    SendSQL("SELECT name, linkinfooter FROM namedqueries " .
            "WHERE userid = $userid");
    while (MoreSQLData()) {
        my ($name, $footer) = (FetchSQLData());
        $old{$name} = $footer;
    }
    
    for (my $c = 0; $c < $::FORM{'numqueries'}; $c++) {
        my $name = $::FORM{"name-$c"};
        if (exists $old{$name}) {
            my $new = $::FORM{"query-$c"};
            if ($new ne $old{$name}) {
                detaint_natural($new);
                SendSQL("UPDATE namedqueries SET linkinfooter = $new " .
                        "WHERE userid = $userid " .
                        "AND name = " . SqlQuote($name));
            }
        } else {
            DisplayError("Hmm, the $name query seems to have gone away.");
        }
    }
    SendSQL("UPDATE profiles SET mybugslink = " . 
            SqlQuote($::FORM{'mybugslink'}) . " WHERE userid = $userid");

    # Regenerate cached info about queries in footer.            
    $vars->{'user'} = GetUserInfo($::userid);
}
    
    
sub DoPermissions {
    my (@has_bits, @set_bits);
    
    SendSQL("SELECT description FROM groups " .
            "WHERE bit & $::usergroupset != 0 " .
            "ORDER BY bit");
    while (MoreSQLData()) {
        push(@has_bits, FetchSQLData());
    }
    
    SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $userid");
    my $blessgroupset = FetchOneColumn();
    if ($blessgroupset) {
        SendSQL("SELECT description FROM groups " .
                "WHERE bit & $blessgroupset != 0 " .
                "ORDER BY bit");
        while (MoreSQLData()) {
            push(@set_bits, FetchSQLData());
        }
    }
    
    $vars->{'has_bits'} = \@has_bits;
    $vars->{'set_bits'} = \@set_bits;    
}

# No SavePermissions() because this panel has no changeable fields.

###############################################################################
# Live code (not subroutine definitions) starts here
###############################################################################

ConnectToDatabase();
confirm_login();

GetVersionTable();

$vars->{'login'} = $::COOKIE{'Bugzilla_login'};
$vars->{'changes_saved'} = $::FORM{'dosave'};

my $current_tab_name = $::FORM{'tab'} || "account";

# The SWITCH below makes sure that this is valid
trick_taint($current_tab_name);

$vars->{'current_tab_name'} = $current_tab_name;

# Do any saving, and then display the current tab.
SWITCH: for ($current_tab_name) {
    /^account$/ && do {
        SaveAccount() if $::FORM{'dosave'};
        DoAccount();
        last SWITCH;
    };
    /^email$/ && do {
        SaveEmail() if $::FORM{'dosave'};
        DoEmail();
        last SWITCH;
    };
    /^footer$/ && do {
        SaveFooter() if $::FORM{'dosave'};
        DoFooter();
        last SWITCH;
    };
    /^permissions$/ && do {
        DoPermissions();
        last SWITCH;
    };
    ThrowUserError("Unknown tab <code>" . html_quote($current_tab_name)
                   . "</code>");
}

# Generate and return the UI (HTML page) from the appropriate template.
print "Content-type: text/html\n\n";
$template->process("account/prefs/prefs.html.tmpl", $vars)
  || ThrowTemplateError($template->error());

