userprefs.cgi 15.3 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
#                 Shane H. W. Travis <travis@sedsystems.ca>
24 25 26

use strict;

27 28
use lib qw(.);

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

35 36
require "CGI.pl";

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

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

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

sub SaveAccount {
71
    my $cgi = Bugzilla->cgi;
72

73 74 75 76
    my $pwd1 = $cgi->param('new_password1');
    my $pwd2 = $cgi->param('new_password2');

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

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

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

101
            # Invalidate all logins except for the current one
102
            Bugzilla->logout(LOGOUT_KEEP_CURRENT);
103
        }
104 105
    }

106 107 108
    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'));
109 110

        if($old_login_name ne $new_login_name) {
111
            $cgi->param('Bugzilla_password') 
112
              || ThrowCodeError("old_password_required");
113

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

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

126
            Bugzilla::Token::IssueEmailChangeToken($userid,$old_login_name,
127 128
                                                 $new_login_name);

129
            $vars->{'email_changes_saved'} = 1;
130 131
        }
    }
132

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


139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171
sub DoSettings {
    $vars->{'settings'} = Bugzilla->user->settings;

    my @setting_list = keys %{Bugzilla->user->settings};
    $vars->{'setting_names'} = \@setting_list;
}

sub SaveSettings {
    my $cgi = Bugzilla->cgi;

    my $settings = Bugzilla->user->settings;
    my @setting_list = keys %{Bugzilla->user->settings};

    foreach my $name (@setting_list) {
        next if ! ($settings->{$name}->{'is_enabled'});
        my $value = $cgi->param($name);

        # de-taint the value.
        if ($value =~ /^([-\w]+)$/ ) {
            $value = $1;
        }
        if ($value eq "${name}-isdefault" ) {
            if (! $settings->{$name}->{'is_default'}) {
                 $settings->{$name}->reset_to_default;
            }
        }
        else {
           $settings->{$name}->set($value);
        }
    }
    $vars->{'settings'} = Bugzilla->user->settings(1);
}

172
sub DoEmail {
173
    my $dbh = Bugzilla->dbh;
174 175 176 177
    
    ###########################################################################
    # User watching
    ###########################################################################
178
    if (Param("supportwatchers")) {
179
        my $watched_ref = $dbh->selectcol_arrayref(
180 181 182
            "SELECT profiles.login_name FROM watch INNER JOIN profiles" .
            " ON watch.watched = profiles.userid" .
            " WHERE watcher = ?",
183 184
            undef, $userid);
        $vars->{'watchedusers'} = join(',', @$watched_ref);
185 186 187 188 189 190 191 192 193 194 195 196 197

        my $watcher_ids = $dbh->selectcol_arrayref(
            "SELECT watcher FROM watch WHERE watched = ?",
            undef, $userid);

        my @watchers;
        foreach my $watcher_id (@$watcher_ids) {
            my $watcher = new Bugzilla::User($watcher_id);
            push (@watchers, Bugzilla::User::identity($watcher));
        }

        @watchers = sort { lc($a) cmp lc($b) } @watchers;
        $vars->{'watchers'} = \@watchers;
198
    }
199

200 201 202 203 204 205 206 207 208 209 210 211
    ###########################################################################
    # Role-based preferences
    ###########################################################################
    my $sth = Bugzilla->dbh->prepare("SELECT relationship, event " . 
                                     "FROM email_setting " . 
                                     "WHERE user_id = $userid");
    $sth->execute();

    my %mail;
    while (my ($relationship, $event) = $sth->fetchrow_array()) {
        $mail{$relationship}{$event} = 1;
    }
212

213 214
    $vars->{'mail'} = \%mail;      
}
215

216 217 218
sub SaveEmail {
    my $dbh = Bugzilla->dbh;
    my $cgi = Bugzilla->cgi;
219
    
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234
    ###########################################################################
    # Role-based preferences
    ###########################################################################
    $dbh->bz_lock_tables("email_setting WRITE");

    # Delete all the user's current preferences
    $dbh->do("DELETE FROM email_setting WHERE user_id = $userid");

    # Repopulate the table - first, with normal events in the 
    # relationship/event matrix.
    # Note: the database holds only "off" email preferences, as can be implied 
    # from the name of the table - profiles_nomail.
    foreach my $rel (RELATIONSHIPS) {
        # Positive events: a ticked box means "send me mail."
        foreach my $event (POS_EVENTS) {
235 236 237
            if (defined($cgi->param("email-$rel-$event"))
                && $cgi->param("email-$rel-$event") == 1)
            {
238 239 240
                $dbh->do("INSERT INTO email_setting " . 
                         "(user_id, relationship, event) " . 
                         "VALUES ($userid, $rel, $event)");
241
            }
242 243 244 245 246 247 248 249 250 251
        }
        
        # Negative events: a ticked box means "don't send me mail."
        foreach my $event (NEG_EVENTS) {
            if (!defined($cgi->param("neg-email-$rel-$event")) ||
                $cgi->param("neg-email-$rel-$event") != 1) 
            {
                $dbh->do("INSERT INTO email_setting " . 
                         "(user_id, relationship, event) " . 
                         "VALUES ($userid, $rel, $event)");
252
            }
253
        }
254
    }
255

256 257 258 259 260 261
    # Global positive events: a ticked box means "send me mail."
    foreach my $event (GLOBAL_EVENTS) {
        if (1 == $cgi->param("email-" . REL_ANY . "-$event")) {
            $dbh->do("INSERT INTO email_setting " . 
                     "(user_id, relationship, event) " . 
                     "VALUES ($userid, " . REL_ANY . ", $event)");
262
        }
263
    }
264

265 266 267 268 269
    $dbh->bz_unlock_tables();

    ###########################################################################
    # User watching
    ###########################################################################
270
    if (Param("supportwatchers") && defined $cgi->param('watchedusers')) {
271 272 273 274
        # 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.
275
        $dbh->bz_lock_tables('watch WRITE', 'profiles READ');
276 277

        # what the db looks like now
278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303
        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);
        }
304

305
        $dbh->bz_unlock_tables();
306
    }
307 308 309
}


310 311 312
sub DoPermissions {
    my (@has_bits, @set_bits);
    
313 314 315 316
    SendSQL("SELECT DISTINCT name, description FROM groups " .
            "INNER JOIN user_group_map " .
            "ON user_group_map.group_id = groups.id " .
            "WHERE user_id = $::userid " .
317 318
            "AND isbless = 0 " .
            "ORDER BY name");
319
    while (MoreSQLData()) {
320 321
        my ($nam, $desc) = FetchSQLData();
        push(@has_bits, {"desc" => $desc, "name" => $nam});
322
    }
323 324 325 326 327
    my @set_ids = ();
    SendSQL("SELECT DISTINCT name, description FROM groups " .
            "ORDER BY name");
    while (MoreSQLData()) {
        my ($nam, $desc) = FetchSQLData();
328
        if (Bugzilla->user->can_bless($nam)) {
329
            push(@set_bits, {"desc" => $desc, "name" => $nam});
330 331
        }
    }
332 333 334
    
    $vars->{'has_bits'} = \@has_bits;
    $vars->{'set_bits'} = \@set_bits;    
335
}
336

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

339 340

sub DoSavedSearches() {
341 342
    # 2004-12-13 - colin.ogilvie@gmail.com, bug 274397
    # Need to work around the possibly missing query_format=advanced
343
    $vars->{'user'} = Bugzilla->user;
344 345 346
    my @queries = @{Bugzilla->user->queries};
    my @newqueries;
    foreach my $q (@queries) {
347 348 349 350 351 352 353 354 355
        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';
                }
356
            }
357 358
        } else {
            $q->{'query'} .= '&query_format=advanced';
359 360 361 362
        }
        push @newqueries, $q;
    }
    $vars->{'queries'} = \@newqueries;
363 364
}

365 366 367 368 369 370 371 372 373 374 375 376 377 378
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;
379 380 381 382 383
    
    my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0;
    $dbh->do("UPDATE profiles SET mybugslink = $showmybugslink " . 
             "WHERE userid = " . Bugzilla->user->id);    
    Bugzilla->user->{'showmybugslink'} = $showmybugslink;
384
}
385 386


387 388 389
###############################################################################
# Live code (not subroutine definitions) starts here
###############################################################################
390

391 392 393 394 395 396 397
my $cgi = Bugzilla->cgi;

# This script needs direct access to the username and password CGI variables,
# so we save them before their removal in Bugzilla->login
my $bugzilla_login    = $cgi->param('Bugzilla_login');
my $bugzilla_password = $cgi->param('Bugzilla_password');

398
Bugzilla->login(LOGIN_REQUIRED);
399 400
$cgi->param('Bugzilla_login', $bugzilla_login);
$cgi->param('Bugzilla_password', $bugzilla_password);
401 402 403

GetVersionTable();

404
$vars->{'changes_saved'} = $cgi->param('dosave');
405

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

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

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

413 414 415
# Do any saving, and then display the current tab.
SWITCH: for ($current_tab_name) {
    /^account$/ && do {
416
        SaveAccount() if $cgi->param('dosave');
417 418 419
        DoAccount();
        last SWITCH;
    };
420 421 422 423 424
    /^settings$/ && do {
        SaveSettings() if $cgi->param('dosave');
        DoSettings();
        last SWITCH;
    };
425
    /^email$/ && do {
426
        SaveEmail() if $cgi->param('dosave');
427 428 429 430 431 432 433
        DoEmail();
        last SWITCH;
    };
    /^permissions$/ && do {
        DoPermissions();
        last SWITCH;
    };
434
    /^saved-searches$/ && do {
435
        SaveSavedSearches() if $cgi->param('dosave');
436 437 438
        DoSavedSearches();
        last SWITCH;
    };
439 440
    ThrowUserError("unknown_tab",
                   { current_tab_name => $current_tab_name });
441 442
}

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