#!/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 # Dan Mosedale # Alan Raetz # David Miller # Christopher Aillon # Gervase Markham 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", "Unconfirmed"); 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, 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 ($pwd1 ne $pwd2) { DisplayError("The two passwords you entered did not match."); exit; } if ($::FORM{'new_password1'} eq '') { DisplayError("You must enter a new password."); exit; } my $passworderror = ValidatePassword($pwd1); (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("current_tab_name"); } # 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());