userprefs.cgi 13.4 KB
Newer Older
1
#!/usr/bin/perl -wT
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
# -*- 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>
17
#                 Dan Mosedale <dmose@mozilla.org>
18
#                 Alan Raetz <al_raetz@yahoo.com>
19
#                 David Miller <justdave@syndicomm.com>
20
#                 Christopher Aillon <christopher@aillon.com>
21
#                 Gervase Markham <gerv@gerv.net>
22
#                 Vlad Dascalu <jocuri@softhome.net>
23 24 25

use strict;

26 27
use lib qw(.);

28
use Bugzilla;
29
use Bugzilla::Constants;
30
use Bugzilla::Search;
31
use Bugzilla::Auth;
32
use Bugzilla::User;
33

34 35
require "CGI.pl";

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

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

43 44 45 46 47 48 49
###############################################################################
# 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 {
50
    my $dbh = Bugzilla->dbh;
51
    SendSQL("SELECT realname FROM profiles WHERE userid = $userid");
52
    $vars->{'realname'} = FetchSQLData();
53 54

    if(Param('allowemailchange')) {
55 56
        SendSQL("SELECT tokentype, issuedate + " . $dbh->sql_interval('3 DAY') .
                ", eventdata
57 58 59
                    FROM tokens
                    WHERE userid = $userid
                    AND tokentype LIKE 'email%' 
60
                    ORDER BY tokentype ASC " . $dbh->sql_limit(1));
61 62 63 64 65 66 67 68 69 70
        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;
            }
        }
    }
71 72 73
}

sub SaveAccount {
74
    my $cgi = Bugzilla->cgi;
75

76 77 78 79
    my $pwd1 = $cgi->param('new_password1');
    my $pwd2 = $cgi->param('new_password2');

    if ($cgi->param('Bugzilla_password') ne "" || 
80
        $pwd1 ne "" || $pwd2 ne "") 
81
    {
82
        my $old = SqlQuote($cgi->param('Bugzilla_password'));
83 84
        SendSQL("SELECT cryptpassword FROM profiles WHERE userid = $userid");
        my $oldcryptedpwd = FetchOneColumn();
85 86
        $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");

87
        if (crypt($cgi->param('Bugzilla_password'), $oldcryptedpwd) ne 
88 89
                  $oldcryptedpwd) 
        {
90
            ThrowUserError("old_password_incorrect");
91
        }
92 93 94

        if ($pwd1 ne "" || $pwd2 ne "")
        {
95 96
            $cgi->param('new_password1')
              || ThrowUserError("new_password_missing");
97
            ValidatePassword($pwd1, $pwd2);
98
        
99
            my $cryptedpassword = SqlQuote(bz_crypt($pwd1));
100 101 102
            SendSQL("UPDATE profiles 
                     SET    cryptpassword = $cryptedpassword 
                     WHERE  userid = $userid");
103

104
            # Invalidate all logins except for the current one
105
            Bugzilla->logout(LOGOUT_KEEP_CURRENT);
106
        }
107 108
    }

109 110 111
    if(Param("allowemailchange") && $cgi->param('new_login_name')) {
        my $old_login_name = $cgi->param('Bugzilla_login');
        my $new_login_name = trim($cgi->param('new_login_name'));
112 113

        if($old_login_name ne $new_login_name) {
114
            $cgi->param('Bugzilla_password') 
115
              || ThrowCodeError("old_password_required");
116

117
            use Bugzilla::Token;
118
            # Block multiple email changes for the same user.
119
            if (Bugzilla::Token::HasEmailChangeToken($userid)) {
120
                ThrowUserError("email_change_in_progress");
121 122 123 124 125
            }

            # Before changing an email address, confirm one does not exist.
            CheckEmailSyntax($new_login_name);
            trick_taint($new_login_name);
126
            is_available_username($new_login_name)
127
              || ThrowUserError("account_exists", {email => $new_login_name});
128

129
            Bugzilla::Token::IssueEmailChangeToken($userid,$old_login_name,
130 131
                                                 $new_login_name);

132
            $vars->{'email_changes_saved'} = 1;
133 134
        }
    }
135

136
    SendSQL("UPDATE profiles SET " .
137
            "realname = " . SqlQuote(trim($cgi->param('realname'))) .
138 139 140 141
            " WHERE userid = $userid");
}


142
sub DoEmail {
143 144
    my $dbh = Bugzilla->dbh;

145
    if (Param("supportwatchers")) {
146 147 148 149 150
        my $watched_ref = $dbh->selectcol_arrayref(
            "SELECT profiles.login_name FROM watch, profiles"
          . " WHERE watcher = ? AND watch.watched = profiles.userid",
            undef, $userid);
        $vars->{'watchedusers'} = join(',', @$watched_ref);
151
    }
152

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

155
    my ($flagstring) = FetchSQLData();
156

157 158 159 160
    # 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);
161

162 163 164 165
    # 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.
166 167
    if (exists($emailflags{'ExcludeSelf'}) 
        && $emailflags{'ExcludeSelf'} eq 'on')
168 169 170 171 172 173 174
    {
        $vars->{'excludeself'} = 1;
    }
    else {
        $vars->{'excludeself'} = 0;
    }
    
175
    foreach my $flag (qw(FlagRequestee FlagRequester)) {
176 177 178 179
        $vars->{$flag} = 
          !exists($emailflags{$flag}) || $emailflags{$flag} eq 'on';
    }
    
180
    # Parse the info into a hash of hashes; the first hash keyed by role,
181 182 183 184 185 186 187 188 189 190 191 192
    # 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;
            }
193
        }
194
    }
195 196
}

197 198 199
# Note: we no longer store "off" values in the database.
sub SaveEmail {
    my $updateString = "";
200
    my $cgi = Bugzilla->cgi;
201 202
    my $dbh = Bugzilla->dbh;

203
    if (defined $cgi->param('ExcludeSelf')) {
204
        $updateString .= 'ExcludeSelf~on';
205
    } else {
206 207
        $updateString .= 'ExcludeSelf~';
    }
208
    
209
    foreach my $flag (qw(FlagRequestee FlagRequester)) {
210
        $updateString .= "~$flag~" . (defined $cgi->param($flag) ? "on" : "");
211 212
    }
    
213 214 215 216 217 218 219 220
    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".
221
            $updateString .= "on" if defined $cgi->param("email$role$reason");
222
        }
223
    }
224 225 226 227
            
    SendSQL("UPDATE profiles SET emailflags = " . SqlQuote($updateString) . 
            " WHERE userid = $userid");

228
    if (Param("supportwatchers") && defined $cgi->param('watchedusers')) {
229 230 231 232
        # 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.
233
        $dbh->bz_lock_tables('watch WRITE', 'profiles READ');
234 235

        # what the db looks like now
236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261
        my $old_watch_ids =
            $dbh->selectcol_arrayref("SELECT watched FROM watch"
                                   . " WHERE watcher = ?", undef, $userid);
 
       # The new information given to us by the user.
        my @new_watch_names = split(/[,\s]+/, $cgi->param('watchedusers'));
        my @new_watch_ids = ();
        foreach my $username (@new_watch_names) {
            my $watched_userid = DBNameToIdAndCheck(trim($username));
            push(@new_watch_ids, $watched_userid);
        }
        my ($removed, $added) = diff_arrays($old_watch_ids, \@new_watch_ids);

        # Remove people who were removed.
        my $delete_sth = $dbh->prepare('DELETE FROM watch WHERE watched = ?'
                                     . ' AND watcher = ?');
        foreach my $remove_me (@$removed) {
            $delete_sth->execute($remove_me, $userid);
        }

        # Add people who were added.
        my $insert_sth = $dbh->prepare('INSERT INTO watch (watched, watcher)'
                                     . ' VALUES (?, ?)');
        foreach my $add_me (@$added) {
            $insert_sth->execute($add_me, $userid);
        }
262

263
        $dbh->bz_unlock_tables();
264
    }
265 266 267
}


268 269 270
sub DoPermissions {
    my (@has_bits, @set_bits);
    
271 272 273 274 275
    SendSQL("SELECT DISTINCT name, description FROM groups, user_group_map " .
            "WHERE user_group_map.group_id = groups.id " .
            "AND user_id = $::userid " .
            "AND isbless = 0 " .
            "ORDER BY name");
276
    while (MoreSQLData()) {
277 278
        my ($nam, $desc) = FetchSQLData();
        push(@has_bits, {"desc" => $desc, "name" => $nam});
279
    }
280 281 282 283 284
    my @set_ids = ();
    SendSQL("SELECT DISTINCT name, description FROM groups " .
            "ORDER BY name");
    while (MoreSQLData()) {
        my ($nam, $desc) = FetchSQLData();
285
        if (Bugzilla->user->can_bless($nam)) {
286
            push(@set_bits, {"desc" => $desc, "name" => $nam});
287 288
        }
    }
289 290 291
    
    $vars->{'has_bits'} = \@has_bits;
    $vars->{'set_bits'} = \@set_bits;    
292
}
293

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

296 297

sub DoSavedSearches() {
298 299
    # 2004-12-13 - colin.ogilvie@gmail.com, bug 274397
    # Need to work around the possibly missing query_format=advanced
300
    $vars->{'user'} = Bugzilla->user;
301 302 303
    my @queries = @{Bugzilla->user->queries};
    my @newqueries;
    foreach my $q (@queries) {
304 305 306 307 308 309 310 311 312
        if ($q->{'query'} =~ /query_format=([^&]*)/) {
            my $format = $1;
            if (!IsValidQueryType($format)) {
                if ($format eq "") {
                    $q->{'query'} =~ s/query_format=/query_format=advanced/;
                }
                else {
                    $q->{'query'} .= '&query_format=advanced';
                }
313
            }
314 315
        } else {
            $q->{'query'} .= '&query_format=advanced';
316 317 318 319
        }
        push @newqueries, $q;
    }
    $vars->{'queries'} = \@newqueries;
320 321
}

322 323 324 325 326 327 328 329 330 331 332 333 334 335
sub SaveSavedSearches() {
    my $cgi = Bugzilla->cgi;
    my $dbh = Bugzilla->dbh;
    my @queries = @{Bugzilla->user->queries};
    my $sth = $dbh->prepare("UPDATE namedqueries SET linkinfooter = ?
                          WHERE userid = ?
                          AND name = ?");
    foreach my $q (@queries) {
        my $linkinfooter = 
            defined($cgi->param("linkinfooter_$q->{'name'}")) ? 1 : 0;
            $sth->execute($linkinfooter, $userid, $q->{'name'});
    }

    Bugzilla->user->flush_queries_cache;
336 337 338 339 340
    
    my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0;
    $dbh->do("UPDATE profiles SET mybugslink = $showmybugslink " . 
             "WHERE userid = " . Bugzilla->user->id);    
    Bugzilla->user->{'showmybugslink'} = $showmybugslink;
341
}
342 343


344 345 346
###############################################################################
# Live code (not subroutine definitions) starts here
###############################################################################
347

348
Bugzilla->login(LOGIN_REQUIRED);
349 350 351

GetVersionTable();

352 353
my $cgi = Bugzilla->cgi;

354
$vars->{'changes_saved'} = $cgi->param('dosave');
355

356
my $current_tab_name = $cgi->param('tab') || "account";
357

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

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

363 364 365
# Do any saving, and then display the current tab.
SWITCH: for ($current_tab_name) {
    /^account$/ && do {
366
        SaveAccount() if $cgi->param('dosave');
367 368 369 370
        DoAccount();
        last SWITCH;
    };
    /^email$/ && do {
371
        SaveEmail() if $cgi->param('dosave');
372 373 374 375 376 377 378
        DoEmail();
        last SWITCH;
    };
    /^permissions$/ && do {
        DoPermissions();
        last SWITCH;
    };
379
    /^saved-searches$/ && do {
380
        SaveSavedSearches() if $cgi->param('dosave');
381 382 383
        DoSavedSearches();
        last SWITCH;
    };
384 385
    ThrowUserError("unknown_tab",
                   { current_tab_name => $current_tab_name });
386 387
}

388
# Generate and return the UI (HTML page) from the appropriate template.
389
print $cgi->header();
390 391
$template->process("account/prefs/prefs.html.tmpl", $vars)
  || ThrowTemplateError($template->error());