]> git.ipfire.org Git - thirdparty/bugzilla.git/commitdiff
Bug 117060 - templatise userprefs.cgi. We also get a nice new set of tabs and a prope...
authorgerv%gerv.net <>
Thu, 28 Feb 2002 08:13:56 +0000 (08:13 +0000)
committergerv%gerv.net <>
Thu, 28 Feb 2002 08:13:56 +0000 (08:13 +0000)
template/default/prefs/account.tmpl [new file with mode: 0644]
template/default/prefs/email.tmpl [new file with mode: 0644]
template/default/prefs/footer.tmpl [new file with mode: 0644]
template/default/prefs/permissions.tmpl [new file with mode: 0644]
template/default/prefs/userprefs.tmpl [new file with mode: 0644]
userprefs.cgi

diff --git a/template/default/prefs/account.tmpl b/template/default/prefs/account.tmpl
new file mode 100644 (file)
index 0000000..486aba0
--- /dev/null
@@ -0,0 +1,56 @@
+[%# 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): Gervase Markham <gerv@gerv.net>
+  #%]
+
+[%# INTERFACE:
+  # realname: string. The user's real name, if any.
+  # login:    string. The user's Bugzilla login email address.
+  #%]
+
+<table>
+  <tr>
+    <th align="right">Old password:</th>
+    <td>
+      <input type="hidden" name="Bugzilla_login" 
+             value="[% login FILTER html %]" />
+      <input type="password" name="Bugzilla_password" />  
+    </td>
+  </tr>              
+
+  <tr>
+    <th align="right">New password:</th>
+    <td>
+      <input type="password" name="new_password1" />
+    </td>
+  </tr>              
+
+  <tr>
+    <th align="right">Re-enter new password:</th>
+    <td>
+      <input type="password" name="new_password2" />
+    </td>
+  </tr>              
+
+  <tr>
+    <th align="right">Your real name (optional, but encouraged):</th>
+    <td>
+      <input size="35" name="realname" value="[% realname FILTER html %]" />
+    </td>
+  </tr>  
+</table>
diff --git a/template/default/prefs/email.tmpl b/template/default/prefs/email.tmpl
new file mode 100644 (file)
index 0000000..2210f27
--- /dev/null
@@ -0,0 +1,156 @@
+[%# 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): Gervase Markham <gerv@gerv.net>
+  #%]
+
+[%# INTERFACE:
+  # watchedusers: string. 
+  #               Comma-separated list of email addresses this user watches.
+  # excludeself:  boolean.
+  #               True if user is not receiving self-generated mail.
+  # <rolename>:   Multiple hashes, one for each rolename (e.g. owner; see 
+  #               below), keyed by reasonname (e.g. comments; again, see 
+  #               below). The value is a boolean - true if the user is 
+  #               receiving mail for that reason when in that role.
+  # Also references the 'supportwatchers' Param.
+  #%]
+<table>
+  [% IF Param('supportwatchers') %]
+    <tr>
+      <td colspan="4">
+        <hr />
+      </td>
+    </tr>
+
+    <tr>
+      <td colspan="4">
+        If you want to help cover for someone when they're on vacation, or if
+        you need to do the QA related to all of their bugs, you can tell 
+        Bugzilla to send mail related to their bugs to you also.  List the 
+        email addresses of any users you wish to watch here, separated by
+        commas.
+      </td>
+    </tr>
+
+    <tr>
+      <th align="right">Users to watch:</th>
+      <td>
+        <input size="35" name="watchedusers" value="[% watchedusers %]" />
+      </td>
+    </tr>
+  [% END %]
+
+  <tr>
+    <td colspan="2">
+      <p>
+        If you don't like getting a notification for "trivial"
+        changes to bugs, you can use the settings below to
+        filter some (or even all) notifications.
+      </p>
+    </td>
+  </tr>
+</table>
+
+<hr />
+
+<table>
+  <tr>
+    <td colspan="2">
+      <b>Global options:</b>
+    </td>
+  </tr>
+
+  <tr>
+    <td width="150"></td>
+    <td>
+      Only email me reports of changes made by other people
+      <input type="checkbox" name="ExcludeSelf" value="on"
+        [% " checked" IF excludeself %]>
+      <br />
+    </td>
+  </tr>
+</table>
+
+<hr />
+<b>Field/recipient specific options:</b>
+<br />
+<br />
+
+
+<table width="100%" border="1">
+  <tr>
+    <td colspan="5" align="center" width="50%">
+      <b>When my relationship to this bug is:</b>
+    </td>
+    <td rowspan="2" width="50%">
+      <b>I want to receive mail when:</b>
+    </td>
+  </tr>
+
+  <tr>
+    <td align="center" width="10%">
+      <b>Reporter</b>
+    </td>
+    <td align="center" width="10%">
+      <b>Assignee</b>
+    </td>
+    <td align="center" width="10%">
+      <b>QA Contact</b>
+    </td>
+    <td align="center" width="10%">
+      <b>CC</b>
+    </td>
+    <td align="center" width="10%">
+      <b>Voter</b>
+    </td>
+  </tr>
+
+  [% FOREACH reason = [ 
+      { name = 'Removeme',    
+        description = 'I\'m added to or removed from this capacity' },
+      { name = 'Comments',    
+        description = 'New Comments are added' },
+      { name = 'Attachments', 
+        description = 'New Attachments are added' },
+      { name = 'Status',      
+        description = 'Priority, status, severity, and/or milestone changes' },
+      { name = 'Resolved',    
+        description = 'The bug is resolved or verified' },
+      { name = 'Keywords',    
+        description = 'Keywords field changes' },
+      { name = 'CC',          
+        description = 'CC field changes' },
+      { name = 'Other',       
+        description = 'Any field not mentioned above changes' } ] %]
+    <tr>
+      [% FOREACH role = [ "Owner", "Reporter", "QAcontact", "CClist", "Voter" ] 
+       %]
+        <td align="center">
+          <input type="checkbox" name="email[% role %][% reason.name %]" value="on"
+            [% " checked" IF $role.${reason.name} %]>
+        </td>
+      [% END %]
+      <td>
+        [% reason.description %]
+      </td>
+    </tr>
+  [% END %]
+</table>       
+
+<br />
diff --git a/template/default/prefs/footer.tmpl b/template/default/prefs/footer.tmpl
new file mode 100644 (file)
index 0000000..7b0756d
--- /dev/null
@@ -0,0 +1,75 @@
+[%# 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): Gervase Markham <gerv@gerv.net>
+  #%]
+
+[%# INTERFACE:
+  # mybugslink: boolean. True if the user wishes the My Bugs link to appear. 
+  # queries: array of hashes. May be empty. Each hash has two members:
+  #   name:   string. The name of the query.
+  #   footer: boolean. True if the query appears in the footer.
+  #%]
+<table>
+  <tr>
+    <th align="right">The 'My bugs' link:</th>
+    <td>
+      <select name="mybugslink">
+        <option value="1">should appear on the footer of every page</option>  
+        <option value="0" 
+          [% " selected" IF NOT mybugslink %]>should not be displayed
+        </option>  
+      </select>
+    </td>
+  </tr>    
+    
+  <input type="hidden" name="numqueries" value="[% queries.size %]" />
+  [% IF queries.size %]
+    [% FOREACH query = queries %]
+      <tr>
+        <th align="right">Your query named '[% query.name FILTER html %]':</th>
+        <td>
+          <select name="query-[% loop.index %]">
+            <option value="0">should only appear in the query page</option>  
+            <option value="1" 
+              [% " selected" IF query.footer %]>
+              should appear on the footer of every page
+            </option>  
+          </select>
+        </td>
+      </tr>
+
+      <input type="hidden" name="name-[% loop.index %]" 
+             value="[% query.name FILTER html %]" />
+    [% END %]
+
+  [% ELSE %]
+    <tr>
+      <td colspan="4">
+        <br />                            
+        If you create remembered queries using the 
+        <a href="query.cgi">query page</a>,
+        you can then come to this page and choose to have some of them  
+        appear in the footer of each Bugzilla page.
+        <br />
+        <br />
+      </td>
+    </tr>
+  [% END %]
+    
+</table>
diff --git a/template/default/prefs/permissions.tmpl b/template/default/prefs/permissions.tmpl
new file mode 100644 (file)
index 0000000..56429a7
--- /dev/null
@@ -0,0 +1,56 @@
+[%# 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): Gervase Markham <gerv@gerv.net>
+  #%]
+
+[%# INTERFACE:
+  # has_bits: array of strings. May be empty.
+  #           Descriptions of the permission bits the user has. 
+  # set_bits: array of strings. May be empty. 
+  #           Descriptions of the permission bits the user can set for 
+  #           other people.
+  #%]
+<table>
+  <tr>
+    <td>
+      [% IF has_bits.size %]
+        You have the following permission bits set on your account:
+        <ul>
+          [% FOREACH bit_description = has_bits %]
+            <li>[% bit_description %]</li>
+          [% END %]
+        </ul>
+      [% ELSE %]
+        There are no permission bits set on your account.
+      [% END %]
+
+      [% IF set_bits.size %]
+        And you can turn on or off the following bits for
+        <a HREF="editusers.cgi">other users</a>:
+        <p>
+          <ul>
+          [% FOREACH bit_description = set_bits %]
+            <li>[% bit_description %]</li>
+          [% END %]
+          </ul>
+        </p>
+      [% END %]
+    </td>
+  </tr>
+</table>
diff --git a/template/default/prefs/userprefs.tmpl b/template/default/prefs/userprefs.tmpl
new file mode 100644 (file)
index 0000000..ba14521
--- /dev/null
@@ -0,0 +1,105 @@
+[%# 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): Gervase Markham <gerv@gerv.net>
+  #%]
+
+[%# INTERFACE:
+  # login: string. The user's Bugzilla login email address.
+  # tabs: List of hashes. May not be empty. Each hash has three members:
+  #   name: string. Name of the tab (used internally.)
+  #   description: string. Description of the tab (used in tab title.)
+  #   saveable: boolean. True if tab has a form which can be submitted.
+  #               True if user is not receiving self-generated mail.
+  # Note: For each tab name, a template "prefs/${tab.name}.tmpl" must exist, 
+  # and its interface must be fulfilled.
+  # current_tab: A direct reference to one of the hashes in the tabs list.
+  #              This tab will be displayed.
+  # changes_saved: boolean. True if the CGI processed form data before 
+  #                displaying anything.
+  #%]
+[% INCLUDE global/header
+   title = "User Preferences"
+   h2 = login
+   style = "td.selected_tab {
+              border-width: 2px 2px 0px;
+              border-style: solid; 
+            }
+            td.unselected_tab, td.spacer {
+              border-width: 0px 0px 2px 0px;
+              border-style: solid; 
+            }"
+ %]
+
+<center>
+  <table cellspacing="0" cellpadding="10" border="0" width="100%">
+    <tr>
+      <td class="spacer">&nbsp;</td>
+      [% FOREACH tab = tabs %]
+        [% IF tab.name == current_tab.name %]
+          <td align="center" bgcolor="lightblue" class="selected_tab">
+            [% tab.description %]
+          </td>
+        [% ELSE %]
+          <td align="center" bgcolor="#BBBBEE" class="unselected_tab">
+            <a HREF="userprefs.cgi?tab=[% tab.name %]">[% tab.description %]</a>
+          </td>
+        [% END %]
+       [% END %]
+       <td class="spacer">&nbsp;</td>
+     </tr>
+   </table>
+</center>
+
+[% IF changes_saved %]
+  <p>
+    <font color="red">
+      The changes to your 
+      [% current_tab.description FILTER lower %] have been saved.
+    </font>
+  </p>
+[% END %]
+
+<h3>[% current_tab.description %]</h3>
+
+<form method="post">
+  <input type="hidden" name="tab" value="[% current_tab.name %]">
+
+  [% INCLUDE "prefs/${current_tab.name}.tmpl" %]
+
+  [% IF current_tab.saveable %]
+    <input type="hidden" name="dosave" value="1">
+
+    <table>
+      <tr>
+        <td width="150"></td>
+        <td>
+          <input type="submit" value="Submit Changes">
+         </td>
+      </tr>
+    </table>
+  [% END %]
+  
+</form>
+
+[% INCLUDE global/footer %]
+
+    
+    
index eb823326ab6d75a86244759be5dc7107a18b05b3..d86282d08e2162b929b2662ffa17245c787eb07a 100755 (executable)
@@ -18,6 +18,7 @@
 #                 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;
@@ -28,7 +29,7 @@ require "CGI.pl";
 
 use RelationSet;
 
-# Shut up misguided -w warnings about "used only once".  "use vars" just
+# Shut up misguided -w warnings about "used only once". "use vars" just
 # doesn't work for me.
 sub sillyness {
     my $zz;
@@ -36,618 +37,313 @@ sub sillyness {
     $zz = $::usergroupset;
 }
 
+# Use global template variables.
+use vars qw($template $vars);
+
 my $userid;
 
-my $showNewEmailTech;
-
-# Note the use of arrays instead of hashes: we want the items
-# displayed in the same order as they appear in the array.
-
-my @emailGroups = (
-        'Owner',        'the Bug Owner',
-        'Reporter',     'the Reporter',
-        'QAcontact',    'the QA contact',
-        'CClist',       'on the CC list',
-        'Voter',        'a Voter'
-        );
-
-my @emailFlags = (
-        'Removeme',     'When I\'m added to or removed from this capacity',
-        'Comments',     'New Comments',
-        'Attachments',  'New Attachments',
-        'Status',       'Priority, status, severity, and milestone changes',
-        'Resolved',     'When the bug is resolved or verified',
-        'Keywords',     'Keywords field changes',
-        'CC',           'CC field changes',
-        'Other',        'Any field not mentioned above changes'
-        );
-
-my $defaultEmailFlagString =
-        'ExcludeSelf~'               . 'on~' .
-
-        'emailOwnerRemoveme~'        . 'on~' .
-        'emailOwnerComments~'        . 'on~' .
-        'emailOwnerAttachments~'     . 'on~' .
-        'emailOwnerStatus~'          . 'on~' .
-        'emailOwnerResolved~'        . 'on~' .
-        'emailOwnerKeywords~'        . 'on~' .
-        'emailOwnerCC~'              . 'on~' .
-        'emailOwnerOther~'           . 'on~' .
-
-        'emailReporterRemoveme~'     . 'on~' .
-        'emailReporterComments~'     . 'on~' .
-        'emailReporterAttachments~'  . 'on~' .
-        'emailReporterStatus~'       . 'on~' .
-        'emailReporterResolved~'     . 'on~' .
-        'emailReporterKeywords~'     . 'on~' .
-        'emailReporterCC~'           . 'on~' .
-        'emailReporterOther~'        . 'on~' .
-
-        'emailQAcontactRemoveme~'    . 'on~' .
-        'emailQAcontactComments~'    . 'on~' .
-        'emailQAcontactAttachments~' . 'on~' .
-        'emailQAcontactStatus~'      . 'on~' .
-        'emailQAcontactResolved~'    . 'on~' .
-        'emailQAcontactKeywords~'    . 'on~' .
-        'emailQAcontactCC~'          . 'on~' .
-        'emailQAcontactOther~'       . 'on~' .
-
-        'emailCClistRemoveme~'       . 'on~' .
-        'emailCClistComments~'       . 'on~' .
-        'emailCClistAttachments~'    . 'on~' .
-        'emailCClistStatus~'         . 'on~' .
-        'emailCClistResolved~'       . 'on~' .
-        'emailCClistKeywords~'       . 'on~' .
-        'emailCClistCC~'             . 'on~' .
-        'emailCClistOther~'          . 'on~' .
-
-        'emailVoterRemoveme~'        . 'on~' .
-        'emailVoterComments~'        . 'on~' .
-        'emailVoterAttachments~'     . 'on~' .
-        'emailVoterStatus~'          . 'on~' .
-        'emailVoterResolved~'        . 'on~' .
-        'emailVoterKeywords~'        . 'on~' .
-        'emailVoterCC~'              . 'on~' .
-        'emailVoterOther~'           . 'on' ;
-
-sub EmitEntry {
-    my ($description, $entry) = (@_);
-    print qq{<TR><TH ALIGN="right">$description:</TH><TD>$entry</TD></TR>\n};
-}
+# The default email flags leave all email on.
+my $defaultflagstring = "ExcludeSelf~on~";
 
-sub Error {
-    my ($msg) = (@_);
-    print qq{
-$msg
-<P>
-Please hit <B>back</B> and try again.
-};
-    PutFooter();
-    exit();
+foreach my $role ("Owner", "Reporter", "QAcontact", "CClist", "Voter") {
+    foreach my $reason ("Removeme", "Comments", "Attachments", "Status", 
+                        "Resolved", "Keywords", "CC", "Other") 
+    {
+        $defaultflagstring .= "email$role$reason~on~";
+    }
 }
 
+# Remove final "~".
+chop $defaultflagstring;
 
-sub ShowAccount {
+###############################################################################
+# 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");
-    my ($realname) = (FetchSQLData());
-
-    $realname = value_quote($realname);
-        
-    EmitEntry("Old password",
-              qq|<input type=hidden name="Bugzilla_login" value="$::COOKIE{Bugzilla_login}">| .
-              qq|<input type=password name="Bugzilla_password">|);
-    EmitEntry("New password",
-              qq{<input type=password name="pwd1">});
-    EmitEntry("Re-enter new password", 
-              qq{<input type=password name="pwd2">});
-    EmitEntry("Your real name (optional)",
-              qq{<INPUT SIZE=35 NAME="realname" VALUE="$realname">});
+    $vars->{'realname'} = FetchSQLData();
 }
 
 sub SaveAccount {
-    if ($::FORM{'Bugzilla_password'} ne ""
-        || $::FORM{'pwd1'} ne "" || $::FORM{'pwd2'} ne "") {
+    if ($::FORM{'Bugzilla_password'} ne "" || 
+        $::FORM{'new_password1'} ne "" || 
+        $::FORM{'new_password2'} ne "") 
+    {
         my $old = SqlQuote($::FORM{'Bugzilla_password'});
-        my $pwd1 = SqlQuote($::FORM{'pwd1'});
-        my $pwd2 = SqlQuote($::FORM{'pwd2'});
+        my $pwd1 = SqlQuote($::FORM{'new_password1'});
+        my $pwd2 = SqlQuote($::FORM{'new_password2'});
         SendSQL("SELECT cryptpassword FROM profiles WHERE userid = $userid");
         my $oldcryptedpwd = FetchOneColumn();
-        if ( !$oldcryptedpwd ) {
-            Error("I was unable to retrieve your old password from the database.");
+        if (!$oldcryptedpwd) {
+            DisplayError("I was unable to retrieve your old password from the database.");
+            exit;
         }
-        if ( crypt($::FORM{'Bugzilla_password'}, $oldcryptedpwd) ne $oldcryptedpwd ) {
-            Error("You did not enter your old password correctly.");
+        if (crypt($::FORM{'Bugzilla_password'}, $oldcryptedpwd) ne 
+                  $oldcryptedpwd) 
+        {
+            DisplayError("You did not enter your old password correctly.");
+            exit;
         }
         if ($pwd1 ne $pwd2) {
-            Error("The two passwords you entered did not match.");
+            DisplayError("The two passwords you entered did not match.");
+            exit;
         }
-        if ($::FORM{'pwd1'} eq '') {
-            Error("You must enter a new password.");
+        if ($::FORM{'new_password1'} eq '') {
+            DisplayError("You must enter a new password.");
+            exit;
         }
-        my $passworderror = ValidatePassword($::FORM{'pwd1'});
-        Error($passworderror) if $passworderror;
-
-        my $cryptedpassword = SqlQuote(Crypt($::FORM{'pwd1'}));
-        SendSQL("UPDATE  profiles 
-                 SET     cryptpassword = $cryptedpassword 
-                 WHERE   userid = $userid");
+        my $passworderror = ValidatePassword($::FORM{'new_password1'});
+        (DisplayError($passworderror) && exit) if $passworderror;
+        
+        my $cryptedpassword = SqlQuote(Crypt($::FORM{'new_password1'}));
+        SendSQL("UPDATE profiles 
+                 SET    cryptpassword = $cryptedpassword 
+                 WHERE  userid = $userid");
         # Invalidate all logins except for the current one
         InvalidateLogins($userid, $::COOKIE{"Bugzilla_logincookie"});
     }
+
     SendSQL("UPDATE profiles SET " .
             "realname = " . SqlQuote(trim($::FORM{'realname'})) .
             " WHERE userid = $userid");
 }
 
-#
-# Set email flags in database based on the parameter string.
-#
-sub setEmailFlags ($) {
-
-    my $emailFlagString = $_[0];
-
-    SendSQL("UPDATE profiles SET emailflags = " .
-            SqlQuote($emailFlagString) . " WHERE userid = $userid");
-}
-
-
-sub ShowEmailOptions () {
 
+sub DoEmail {
     if (Param("supportwatchers")) {
         my $watcheduserSet = new RelationSet;
         $watcheduserSet->mergeFromDB("SELECT watched FROM watch WHERE" .
                                     " watcher=$userid");
-        my $watchedusers = $watcheduserSet->toString();
-
-        print qq{
-<TR><TD COLSPAN="4"><HR></TD></TR>
-<TR><TD COLSPAN="4">
-If you want to help cover for someone when they're on vacation, or if
-you need to do the QA related to all of their bugs, you can tell bugzilla
-to send mail related to their bugs to you also.  List the email addresses
-of any users you wish to watch here, separated by commas.
-</TD></TR>};
-
-        EmitEntry("Users to watch",
-              qq{<INPUT SIZE=35 NAME="watchedusers" VALUE="$watchedusers">});
+        $vars->{'watchedusers'} = $watcheduserSet->toString();
     }
 
-    print qq{<TR><TD COLSPAN="2"><HR></TD></TR>};
-
-    showAdvancedEmailFilterOptions();
-
-print qq {
-<TABLE CELLSPACING="0" CELLPADDING="10" BORDER=0 WIDTH="100%">
-<TR><TD>};
-
-}
-
-sub showAdvancedEmailFilterOptions () {
-
-    my $flags;
-    my $notify;
-    my %userEmailFlags = ();
-
-    print qq{
-        <TR><TD COLSPAN="2"><center>
-        <font size=+1>Advanced Email Filtering Options</font>
-        </center>
-        </TD></TR><tr><td colspan="2">
-        <p>
-        <center>
-        If you don't like getting a notification for "trivial"
-        changes to bugs, you can use the settings below to
-        filter some (or even all) notifications.
-        </center></td></tr></table>
-       <hr width=800 align=center>
-    };
-
     SendSQL("SELECT emailflags FROM profiles WHERE userid = $userid");
 
-    ($flags) = FetchSQLData();
+    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
+    # 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 ( !$flags ) {
-        $flags = $defaultEmailFlagString;
-        setEmailFlags($flags);
+    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.
-    #
-    %userEmailFlags = split(/~/ , $flags, 255);
-
-    showExcludeSelf(\%userEmailFlags);
-
-    # print STDERR "$flags\n";
-
-    print qq{
-                <hr width=800 align=left>
-                &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-                  <b>Field/recipient specific options:</b><br><br>
-              };
-
-
-    my @tmpGroups = @emailGroups;
-    while ((my $groupName,my $groupText) = splice(@tmpGroups,0,2) ) {
-        printEmailPrefGroup($groupName,$groupText,\%userEmailFlags);
-    }
-
-}
-
-sub showExcludeSelf (\%) {
+    # 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);
 
-     my %CurrentFlags = %{$_[0]};
-     
-     my $excludeSelf = " ";
+    $vars->{'excludeself'} = 0;
 
-     while ( my ($key,$value) = each (%CurrentFlags) ) {
-
-     # print qq{flag name: $key    value: $value<br>};
-
-        if ( $key eq 'ExcludeSelf' ) {
-
-                if ( $value eq 'on' ) {
-
-                        $excludeSelf = "CHECKED";
-                        }
-                }
+    # 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 (undef).
+    foreach my $key (keys %emailflags) {
+        # ExcludeSelf is special.
+        if ($key eq 'ExcludeSelf' && $emailflags{$key} eq 'on') {
+            $vars->{'excludeself'} = 1;
+            next;
         }
 
-        print qq {
-                <table><tr><td colspan=2>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-                <b>Global options:</b></tr>
-                <tr><td width=150></td><td>
-                Only email me reports of changes made by other people
-             <input type="checkbox" name="ExcludeSelf" VALUE="on" $excludeSelf>
-                <br>
-                </td>
-                </tr>
-                </table>
-                };
-
-}
-
-sub printEmailPrefGroup ($$\%) {
-
-    my ($groupName,$textName,$refCurrentFlags) = @_[0,1,2];
-    my @tmpFlags = @emailFlags;
-
-    print qq {<table cellspacing=0 cellpadding=6> };
-    print qq {<tr><td colspan=2> &nbsp;&nbsp;&nbsp;&nbsp;&nbsp;
-                When I\'m $textName, email me:</td></tr> };
-
-    while ((my $flagName,my $flagText) = splice(@tmpFlags,0,2)) {
-
-        printEmailOption($groupName . $flagName, $flagText, $refCurrentFlags);
-    }
-    print qq { </table> };
-    print qq { <hr WIDTH=320 ALIGN=left> };
-}
-
-sub printEmailOption ($$\%) {
-
-    my $value= '';
-
-    my ($optionName,$description,$refCurrentFlags) = @_[0,1,2];
-
-    #print qq{ email$optionName: $$refCurrentFlags{"email$optionName"} <br>};
-
-    # if the db value is 'on', then mark that checkbox
-    if ($$refCurrentFlags{"email$optionName"} eq 'on'){
-        $value = 'CHECKED';
-    }
-
-    # **** Kludge ... also mark on if the value in $$refCurrentFlags in undef
-    if (!defined($$refCurrentFlags{"email$optionName"})) {
-        $value = 'CHECKED';
+        # All other keys match this regexp.
+        $key =~ /email([A-Z]+[a-z]+)([A-Z]+[a-z]*)/;
+        
+        # Create a new hash if we don't have one...
+        if (!defined($vars->{$1})) {
+            $vars->{$1} = {};
+        }
+        
+        if ($emailflags{$key} eq "on") {
+            $vars->{$1}{$2} = 1;
+        }            
     }
-
-    print qq{
-        <tr><td width=320></td>
-        <td><input type="checkbox" name="email$optionName" VALUE="on" $value>
-        $description</td>
-        </tr>
-    };
 }
 
-sub SaveEmailOptions () {
-
-    # I don't understand: global variables and %FORM variables are
-    # not preserved between ShowEmailOptions() and SaveEmailOptions()
-    # The form value here is from a hidden variable just before the SUBMIT.
-
-    my $useNewEmailTech = $::FORM{'savedEmailTech'};
-    my $updateString;
-
-    if ( defined $::FORM{'ExcludeSelf'}) {
+# Note: we no longer store "off" values in the database.
+sub SaveEmail {
+    my $updateString = "";
+    
+    if (defined $::FORM{'ExcludeSelf'}) {
         $updateString .= 'ExcludeSelf~on';
     } else {
         $updateString .= 'ExcludeSelf~';
     }
-    my @tmpGroups = @emailGroups;
-
-    while ((my $groupName,my $groupText) = splice(@tmpGroups,0,2) ) {
-
-        my @tmpFlags = @emailFlags;
-
-        while ((my $flagName,my $flagText) = splice(@tmpFlags,0,2) ) {
-
-            my $entry = 'email' . $groupName . $flagName;
-            my $entryValue;
-
-            if (!defined $::FORM{$entry} ) {
-                $entryValue = "";
-            } else {
-                $entryValue = $::FORM{$entry};
-            }
-
-            $updateString .= '~' . $entry . '~' . $entryValue;
+    
+    foreach my $key (keys %::FORM) {
+        if ($key =~ /email([A-Z]+[a-z]+)([A-Z]+[a-z]*)/) {
+            $updateString .= "~$key~on";    
         }
     }
-        
-    #open(FID,">updateString");
-    #print qq{UPDATE STRING: $updateString <br>};
-    #close(FID);
-
-    SendSQL("UPDATE profiles SET emailflags = " .
-            SqlQuote($updateString) . " WHERE userid = $userid");
-
-    if (Param("supportwatchers") ) {
-
-        if (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]);
-
-            # all done
-            #
-            SendSQL("UNLOCK TABLES");
-        
-        }
+            
+    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 ShowFooter {
+sub DoFooter {
     SendSQL("SELECT mybugslink FROM profiles " .
             "WHERE userid = $userid");
-    my ($mybugslink) = (FetchSQLData());
-    my $entry =
-        BuildPulldown("mybugslink",
-                      [["1", "should appear"],
-                       ["0", "should not be displayed"]],
-                      $mybugslink);
-    EmitEntry("The 'My bugs' link at the footer of each page", $entry);
+    $vars->{'mybugslink'} = FetchSQLData();
+    
     SendSQL("SELECT name, linkinfooter FROM namedqueries " .
             "WHERE userid = $userid");
-    my $count = 0;
+    
+    my @queries;        
     while (MoreSQLData()) {
-        my ($name, $linkinfooter) = (FetchSQLData());
-        if ($name eq $::defaultqueryname) {
-            next;
-        }
-        my $entry =
-            BuildPulldown("query-$count",
-                          [["0", "should only appear in the query page"],
-                           ["1", "should appear on the footer of every page"]],
-                          $linkinfooter);
-        EmitEntry("Your query named '$name'", $entry);
-        my $q = value_quote($name);
-        print qq{<INPUT TYPE=HIDDEN NAME="name-$count" VALUE="$q">\n};
-        $count++;
-    }
-    print qq{<INPUT TYPE=HIDDEN NAME="numqueries" VALUE="$count">\n};
-    if (!$count) {
-        print qq{
-<TR><TD COLSPAN="4">                            
-If you go create remembered queries in the <A HREF="query.cgi">query page</A>,
-you can then come to this page and choose to have some of them appear in the 
-footer of each Bugzilla page.
-</TD></TR>};
+        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, $linkinfooter) = (FetchSQLData());
-        $old{$name} = $linkinfooter;
+        my ($name, $footer) = (FetchSQLData());
+        $old{$name} = $footer;
     }
     
-    for (my $c=0 ; $c<$::FORM{'numqueries'} ; $c++) {
+    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 {
-            Error("Hmm, the $name query seems to have gone away.");
+            DisplayError("Hmm, the $name query seems to have gone away.");
         }
     }
-    SendSQL("UPDATE profiles SET mybugslink = " . SqlQuote($::FORM{'mybugslink'}) .
-            " WHERE userid = $userid");
+    SendSQL("UPDATE profiles SET mybugslink = " . 
+            SqlQuote($::FORM{'mybugslink'}) . " WHERE userid = $userid");
 }
     
-
-
-sub ShowPermissions {
-    print "<TR><TD>You have the following permission bits set on your account:\n";
-    print "<P><UL>\n";
-    my $found = 0;
+    
+sub DoPermissions {
+    my (@has_bits, @set_bits);
+    
     SendSQL("SELECT description FROM groups " .
             "WHERE bit & $::usergroupset != 0 " .
             "ORDER BY bit");
     while (MoreSQLData()) {
-        my ($description) = (FetchSQLData());
-        print "<LI>$description\n";
-        $found = 1;
-    }
-    if ($found == 0) {
-        print "<LI>(No extra permission bits have been set).\n";
+        push(@has_bits, FetchSQLData());
     }
-    print "</UL>\n";
+    
     SendSQL("SELECT blessgroupset FROM profiles WHERE userid = $userid");
     my $blessgroupset = FetchOneColumn();
     if ($blessgroupset) {
-        print "And you can turn on or off the following bits for\n";
-        print qq{<A HREF="editusers.cgi">other users</A>:\n};
-        print "<P><UL>\n";
         SendSQL("SELECT description FROM groups " .
                 "WHERE bit & $blessgroupset != 0 " .
                 "ORDER BY bit");
         while (MoreSQLData()) {
-            my ($description) = (FetchSQLData());
-            print "<LI>$description\n";
+            push(@set_bits, FetchSQLData());
         }
-        print "</UL>\n";
     }
-    print "</TR></TD>\n";
+    
+    $vars->{'has_bits'} = \@has_bits;
+    $vars->{'set_bits'} = \@set_bits;    
 }
-        
-
-
-
-######################################################################
-################# Live code (not sub defs) starts here ###############
 
+# No SavePermissions() because this panel has no changeable fields.
 
+###############################################################################
+# Live code (not subroutine definitions) starts here
+###############################################################################
 confirm_login();
 
-print "Content-type: text/html\n\n";
-
 GetVersionTable();
 
-PutHeader("User Preferences", "User Preferences", $::COOKIE{'Bugzilla_login'});
-
-#  foreach my $k (sort(keys(%::FORM))) {
-#      print "<pre>" . value_quote($k) . ": " . value_quote($::FORM{$k}) . "\n</pre>";
-#  }
-
-my $bank = $::FORM{'bank'} || "account";
-
-my @banklist = (
-                ["account", "Account settings",
-                 \&ShowAccount, \&SaveAccount],
-                ["diffs", "Email settings",
-                                 \&ShowEmailOptions, \&SaveEmailOptions],
-                ["footer", "Page footer",
-                 \&ShowFooter, \&SaveFooter],
-                ["permissions", "Permissions",
-                 \&ShowPermissions, undef]
-                );
+$userid = DBNameToIdAndCheck($::COOKIE{'Bugzilla_login'});
 
+$vars->{'login'} = $::COOKIE{'Bugzilla_login'};
+$vars->{'changes_saved'} = $::FORM{'dosave'};
 
-my $numbanks = @banklist;
-my $numcols = $numbanks + 2;
+my $current_tab_name = $::FORM{'tab'} || "account";
 
-my $headcol = '"lightblue"';
+my @tabs = ( { name => "account", description => "Account settings", 
+               saveable => "1" },
+             { name => "email", description => "Email settings", 
+               saveable => "1" },
+             { name => "footer", description => "Page footer", 
+               saveable => "1" },
+             { name => "permissions", description => "Permissions", 
+               saveable => "0" } );
 
-print qq{
-<CENTER>
-<TABLE CELLSPACING="0" CELLPADDING="10" BORDER=0 WIDTH="100%">
-<TR>
-<TH COLSPAN="$numcols" BGCOLOR="lightblue">User preferences</TH>
-</TR>
-<TR><TD BGCOLOR=$headcol>&nbsp;</TD>
-};
-
-
-my $bankdescription;
-my $showfunc;
-my $savefunc;
-
-foreach my $i (@banklist) {
-    my ($name, $description) = (@$i);
-    my $color = "";
-    if ($name eq $bank) {
-        print qq{<TD ALIGN="center">$description</TD>};
-        my $zz;
-        ($zz, $bankdescription, $showfunc, $savefunc) = (@$i);
-    } else {
-        print qq{<TD ALIGN="center" BGCOLOR="lightblue"><A HREF="userprefs.cgi?bank=$name">$description</A></TD>};
+# Work out the current tab
+foreach my $tab (@tabs) {
+    if ($tab->{'name'} eq $current_tab_name) {
+        $vars->{'current_tab'} = $tab;
+        last;
     }
 }
-print qq{
-<TD BGCOLOR=$headcol>&nbsp;</TD></TR>
-</TABLE>
-</CENTER>
-<P>
-};
-
-
-
 
-if (defined $bankdescription) {
-    $userid = DBNameToIdAndCheck($::COOKIE{'Bugzilla_login'});
+$vars->{'tabs'} = \@tabs;
 
-    if ($::FORM{'dosave'}) {
-        &$savefunc;
-        print "Your changes have been saved.";
-    }
-    print qq{<H3>$bankdescription</H3><FORM METHOD="POST"><TABLE>};
-
-    # execute subroutine from @banklist based on bank selected.
-    &$showfunc;
-
-    print qq{</TABLE><INPUT TYPE="hidden" NAME="dosave" VALUE="1">};
-    print qq{<INPUT TYPE="hidden" NAME="savedEmailTech" VALUE="};
-
-    # default this to 0 if it's not already set
-    #
-    if (defined $showNewEmailTech) {
-        print qq{$showNewEmailTech">};
-    } else {
-        print qq{0">};
-    }
-    print qq{<INPUT TYPE="hidden" NAME="bank" VALUE="$bank"> };
-
-    if ($savefunc) {
-              print qq{<table><tr><td width=150></td><td>
-                        <INPUT TYPE="submit" VALUE="Submit Changes">
-                        </td></tr></table> };
-    }
-    print qq{</FORM>\n};
-} else {
-    print "<P>Please choose from the above links which settings you wish to change.</P>";
+# 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;
+    };
 }
 
+# Generate and return the UI (HTML page) from the appropriate template.
+print "Content-type: text/html\n\n";
+$template->process("prefs/userprefs.tmpl", $vars)
+  || DisplayError("Template process failed: " . $template->error())
+  && exit;
 
-print "<P>";
-
-
-PutFooter();