Rubber-stamped by Brady Eidson.
[webbrowser.git] / BugsSite / userprefs.cgi
blob80fbf026a8950da5b9fbccee5519dccdb65abb75
1 #!/usr/bin/env perl -wT
2 # -*- Mode: perl; indent-tabs-mode: nil -*-
4 # The contents of this file are subject to the Mozilla Public
5 # License Version 1.1 (the "License"); you may not use this file
6 # except in compliance with the License. You may obtain a copy of
7 # the License at http://www.mozilla.org/MPL/
9 # Software distributed under the License is distributed on an "AS
10 # IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
11 # implied. See the License for the specific language governing
12 # rights and limitations under the License.
14 # The Original Code is the Bugzilla Bug Tracking System.
16 # 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>
25 use strict;
27 use lib qw(. lib);
29 use Bugzilla;
30 use Bugzilla::Constants;
31 use Bugzilla::Search;
32 use Bugzilla::Util;
33 use Bugzilla::Error;
34 use Bugzilla::User;
35 use Bugzilla::Token;
37 my $template = Bugzilla->template;
38 local our $vars = {};
40 ###############################################################################
41 # Each panel has two functions - panel Foo has a DoFoo, to get the data
42 # necessary for displaying the panel, and a SaveFoo, to save the panel's
43 # contents from the form data (if appropriate).
44 # SaveFoo may be called before DoFoo.
45 ###############################################################################
46 sub DoAccount {
47 my $dbh = Bugzilla->dbh;
48 my $user = Bugzilla->user;
50 ($vars->{'realname'}) = $dbh->selectrow_array(
51 "SELECT realname FROM profiles WHERE userid = ?", undef, $user->id);
53 if(Bugzilla->params->{'allowemailchange'}
54 && Bugzilla->user->authorizer->can_change_email) {
55 # First delete old tokens.
56 Bugzilla::Token::CleanTokenTable();
58 my @token = $dbh->selectrow_array(
59 "SELECT tokentype, issuedate + " .
60 $dbh->sql_interval(MAX_TOKEN_AGE, 'DAY') . ", eventdata
61 FROM tokens
62 WHERE userid = ?
63 AND tokentype LIKE 'email%'
64 ORDER BY tokentype ASC " . $dbh->sql_limit(1), undef, $user->id);
65 if (scalar(@token) > 0) {
66 my ($tokentype, $change_date, $eventdata) = @token;
67 $vars->{'login_change_date'} = $change_date;
69 if($tokentype eq 'emailnew') {
70 my ($oldemail,$newemail) = split(/:/,$eventdata);
71 $vars->{'new_login_name'} = $newemail;
77 sub SaveAccount {
78 my $cgi = Bugzilla->cgi;
79 my $dbh = Bugzilla->dbh;
80 my $user = Bugzilla->user;
82 my $pwd1 = $cgi->param('new_password1');
83 my $pwd2 = $cgi->param('new_password2');
85 if ($user->authorizer->can_change_password
86 && ($cgi->param('Bugzilla_password') ne "" || $pwd1 ne "" || $pwd2 ne ""))
88 my ($oldcryptedpwd) = $dbh->selectrow_array(
89 q{SELECT cryptpassword FROM profiles WHERE userid = ?},
90 undef, $user->id);
91 $oldcryptedpwd || ThrowCodeError("unable_to_retrieve_password");
93 my $oldpassword = $cgi->param('Bugzilla_password');
95 # Wide characters cause crypt to die
96 if (Bugzilla->params->{'utf8'}) {
97 utf8::encode($oldpassword) if utf8::is_utf8($oldpassword);
100 if (crypt($oldpassword, $oldcryptedpwd) ne $oldcryptedpwd)
102 ThrowUserError("old_password_incorrect");
105 if ($pwd1 ne "" || $pwd2 ne "")
107 $cgi->param('new_password1')
108 || ThrowUserError("new_password_missing");
109 validate_password($pwd1, $pwd2);
111 if ($cgi->param('Bugzilla_password') ne $pwd1) {
112 my $cryptedpassword = bz_crypt($pwd1);
113 $dbh->do(q{UPDATE profiles
114 SET cryptpassword = ?
115 WHERE userid = ?},
116 undef, ($cryptedpassword, $user->id));
118 # Invalidate all logins except for the current one
119 Bugzilla->logout(LOGOUT_KEEP_CURRENT);
124 if ($user->authorizer->can_change_email
125 && Bugzilla->params->{"allowemailchange"}
126 && $cgi->param('new_login_name'))
128 my $old_login_name = $cgi->param('Bugzilla_login');
129 my $new_login_name = trim($cgi->param('new_login_name'));
131 if($old_login_name ne $new_login_name) {
132 $cgi->param('Bugzilla_password')
133 || ThrowUserError("old_password_required");
135 use Bugzilla::Token;
136 # Block multiple email changes for the same user.
137 if (Bugzilla::Token::HasEmailChangeToken($user->id)) {
138 ThrowUserError("email_change_in_progress");
141 # Before changing an email address, confirm one does not exist.
142 validate_email_syntax($new_login_name)
143 || ThrowUserError('illegal_email_address', {addr => $new_login_name});
144 is_available_username($new_login_name)
145 || ThrowUserError("account_exists", {email => $new_login_name});
147 Bugzilla::Token::IssueEmailChangeToken($user, $old_login_name,
148 $new_login_name);
150 $vars->{'email_changes_saved'} = 1;
154 my $realname = trim($cgi->param('realname'));
155 trick_taint($realname); # Only used in a placeholder
156 $dbh->do("UPDATE profiles SET realname = ? WHERE userid = ?",
157 undef, ($realname, $user->id));
161 sub DoSettings {
162 my $user = Bugzilla->user;
164 my $settings = $user->settings;
165 $vars->{'settings'} = $settings;
167 my @setting_list = keys %$settings;
168 $vars->{'setting_names'} = \@setting_list;
170 $vars->{'has_settings_enabled'} = 0;
171 # Is there at least one user setting enabled?
172 foreach my $setting_name (@setting_list) {
173 if ($settings->{"$setting_name"}->{'is_enabled'}) {
174 $vars->{'has_settings_enabled'} = 1;
175 last;
178 $vars->{'dont_show_button'} = !$vars->{'has_settings_enabled'};
181 sub SaveSettings {
182 my $cgi = Bugzilla->cgi;
183 my $user = Bugzilla->user;
185 my $settings = $user->settings;
186 my @setting_list = keys %$settings;
188 foreach my $name (@setting_list) {
189 next if ! ($settings->{$name}->{'is_enabled'});
190 my $value = $cgi->param($name);
191 my $setting = new Bugzilla::User::Setting($name);
193 if ($value eq "${name}-isdefault" ) {
194 if (! $settings->{$name}->{'is_default'}) {
195 $settings->{$name}->reset_to_default;
198 else {
199 $setting->validate_value($value);
200 $settings->{$name}->set($value);
203 $vars->{'settings'} = $user->settings(1);
206 sub DoEmail {
207 my $dbh = Bugzilla->dbh;
208 my $user = Bugzilla->user;
210 ###########################################################################
211 # User watching
212 ###########################################################################
213 if (Bugzilla->params->{"supportwatchers"}) {
214 my $watched_ref = $dbh->selectcol_arrayref(
215 "SELECT profiles.login_name FROM watch INNER JOIN profiles" .
216 " ON watch.watched = profiles.userid" .
217 " WHERE watcher = ?" .
218 " ORDER BY profiles.login_name",
219 undef, $user->id);
220 $vars->{'watchedusers'} = $watched_ref;
222 my $watcher_ids = $dbh->selectcol_arrayref(
223 "SELECT watcher FROM watch WHERE watched = ?",
224 undef, $user->id);
226 my @watchers;
227 foreach my $watcher_id (@$watcher_ids) {
228 my $watcher = new Bugzilla::User($watcher_id);
229 push (@watchers, Bugzilla::User::identity($watcher));
232 @watchers = sort { lc($a) cmp lc($b) } @watchers;
233 $vars->{'watchers'} = \@watchers;
236 ###########################################################################
237 # Role-based preferences
238 ###########################################################################
239 my $sth = $dbh->prepare("SELECT relationship, event " .
240 "FROM email_setting " .
241 "WHERE user_id = ?");
242 $sth->execute($user->id);
244 my %mail;
245 while (my ($relationship, $event) = $sth->fetchrow_array()) {
246 $mail{$relationship}{$event} = 1;
249 $vars->{'mail'} = \%mail;
252 sub SaveEmail {
253 my $dbh = Bugzilla->dbh;
254 my $cgi = Bugzilla->cgi;
255 my $user = Bugzilla->user;
257 if (Bugzilla->params->{"supportwatchers"}) {
258 Bugzilla::User::match_field($cgi, { 'new_watchedusers' => {'type' => 'multi'} });
261 ###########################################################################
262 # Role-based preferences
263 ###########################################################################
264 $dbh->bz_start_transaction();
266 # Delete all the user's current preferences
267 $dbh->do("DELETE FROM email_setting WHERE user_id = ?", undef, $user->id);
269 # Repopulate the table - first, with normal events in the
270 # relationship/event matrix.
271 # Note: the database holds only "off" email preferences, as can be implied
272 # from the name of the table - profiles_nomail.
273 foreach my $rel (RELATIONSHIPS) {
274 # Positive events: a ticked box means "send me mail."
275 foreach my $event (POS_EVENTS) {
276 if (defined($cgi->param("email-$rel-$event"))
277 && $cgi->param("email-$rel-$event") == 1)
279 $dbh->do("INSERT INTO email_setting " .
280 "(user_id, relationship, event) " .
281 "VALUES (?, ?, ?)",
282 undef, ($user->id, $rel, $event));
286 # Negative events: a ticked box means "don't send me mail."
287 foreach my $event (NEG_EVENTS) {
288 if (!defined($cgi->param("neg-email-$rel-$event")) ||
289 $cgi->param("neg-email-$rel-$event") != 1)
291 $dbh->do("INSERT INTO email_setting " .
292 "(user_id, relationship, event) " .
293 "VALUES (?, ?, ?)",
294 undef, ($user->id, $rel, $event));
299 # Global positive events: a ticked box means "send me mail."
300 foreach my $event (GLOBAL_EVENTS) {
301 if (defined($cgi->param("email-" . REL_ANY . "-$event"))
302 && $cgi->param("email-" . REL_ANY . "-$event") == 1)
304 $dbh->do("INSERT INTO email_setting " .
305 "(user_id, relationship, event) " .
306 "VALUES (?, ?, ?)",
307 undef, ($user->id, REL_ANY, $event));
311 $dbh->bz_commit_transaction();
313 ###########################################################################
314 # User watching
315 ###########################################################################
316 if (Bugzilla->params->{"supportwatchers"}
317 && (defined $cgi->param('new_watchedusers')
318 || defined $cgi->param('remove_watched_users')))
320 $dbh->bz_start_transaction();
322 # Use this to protect error messages on duplicate submissions
323 my $old_watch_ids =
324 $dbh->selectcol_arrayref("SELECT watched FROM watch"
325 . " WHERE watcher = ?", undef, $user->id);
327 # The new information given to us by the user.
328 my $new_watched_users = join(',', $cgi->param('new_watchedusers')) || '';
329 my @new_watch_names = split(/[,\s]+/, $new_watched_users);
330 my %new_watch_ids;
332 foreach my $username (@new_watch_names) {
333 my $watched_userid = login_to_id(trim($username), THROW_ERROR);
334 $new_watch_ids{$watched_userid} = 1;
337 # Add people who were added.
338 my $insert_sth = $dbh->prepare('INSERT INTO watch (watched, watcher)'
339 . ' VALUES (?, ?)');
340 foreach my $add_me (keys(%new_watch_ids)) {
341 next if grep($_ == $add_me, @$old_watch_ids);
342 $insert_sth->execute($add_me, $user->id);
345 if (defined $cgi->param('remove_watched_users')) {
346 my @removed = $cgi->param('watched_by_you');
347 # Remove people who were removed.
348 my $delete_sth = $dbh->prepare('DELETE FROM watch WHERE watched = ?'
349 . ' AND watcher = ?');
351 my %remove_watch_ids;
352 foreach my $username (@removed) {
353 my $watched_userid = login_to_id(trim($username), THROW_ERROR);
354 $remove_watch_ids{$watched_userid} = 1;
356 foreach my $remove_me (keys(%remove_watch_ids)) {
357 $delete_sth->execute($remove_me, $user->id);
361 $dbh->bz_commit_transaction();
366 sub DoPermissions {
367 my $dbh = Bugzilla->dbh;
368 my $user = Bugzilla->user;
369 my (@has_bits, @set_bits);
371 my $groups = $dbh->selectall_arrayref(
372 "SELECT DISTINCT name, description FROM groups WHERE id IN (" .
373 $user->groups_as_string . ") ORDER BY name");
374 foreach my $group (@$groups) {
375 my ($nam, $desc) = @$group;
376 push(@has_bits, {"desc" => $desc, "name" => $nam});
378 $groups = $dbh->selectall_arrayref('SELECT DISTINCT id, name, description
379 FROM groups
380 ORDER BY name');
381 foreach my $group (@$groups) {
382 my ($group_id, $nam, $desc) = @$group;
383 if ($user->can_bless($group_id)) {
384 push(@set_bits, {"desc" => $desc, "name" => $nam});
388 # If the user has product specific privileges, inform him about that.
389 foreach my $privs (PER_PRODUCT_PRIVILEGES) {
390 next if $user->in_group($privs);
391 $vars->{"local_$privs"} = $user->get_products_by_permission($privs);
394 $vars->{'has_bits'} = \@has_bits;
395 $vars->{'set_bits'} = \@set_bits;
398 # No SavePermissions() because this panel has no changeable fields.
401 sub DoSavedSearches {
402 my $dbh = Bugzilla->dbh;
403 my $user = Bugzilla->user;
405 if ($user->queryshare_groups_as_string) {
406 $vars->{'queryshare_groups'} =
407 Bugzilla::Group->new_from_list($user->queryshare_groups);
409 $vars->{'bless_group_ids'} = [map {$_->{'id'}} @{$user->bless_groups}];
412 sub SaveSavedSearches {
413 my $cgi = Bugzilla->cgi;
414 my $dbh = Bugzilla->dbh;
415 my $user = Bugzilla->user;
417 # We'll need this in a loop, so do the call once.
418 my $user_id = $user->id;
420 my $sth_insert_nl = $dbh->prepare('INSERT INTO namedqueries_link_in_footer
421 (namedquery_id, user_id)
422 VALUES (?, ?)');
423 my $sth_delete_nl = $dbh->prepare('DELETE FROM namedqueries_link_in_footer
424 WHERE namedquery_id = ?
425 AND user_id = ?');
426 my $sth_insert_ngm = $dbh->prepare('INSERT INTO namedquery_group_map
427 (namedquery_id, group_id)
428 VALUES (?, ?)');
429 my $sth_update_ngm = $dbh->prepare('UPDATE namedquery_group_map
430 SET group_id = ?
431 WHERE namedquery_id = ?');
432 my $sth_delete_ngm = $dbh->prepare('DELETE FROM namedquery_group_map
433 WHERE namedquery_id = ?');
435 # Update namedqueries_link_in_footer for this user.
436 foreach my $q (@{$user->queries}, @{$user->queries_available}) {
437 if (defined $cgi->param("link_in_footer_" . $q->id)) {
438 $sth_insert_nl->execute($q->id, $user_id) if !$q->link_in_footer;
440 else {
441 $sth_delete_nl->execute($q->id, $user_id) if $q->link_in_footer;
445 # For user's own queries, update namedquery_group_map.
446 foreach my $q (@{$user->queries}) {
447 my $group_id;
449 if ($user->in_group(Bugzilla->params->{'querysharegroup'})) {
450 $group_id = $cgi->param("share_" . $q->id) || '';
453 if ($group_id) {
454 # Don't allow the user to share queries with groups he's not
455 # allowed to.
456 next unless grep($_ eq $group_id, @{$user->queryshare_groups});
458 # $group_id is now definitely a valid ID of a group the
459 # user can share queries with, so we can trick_taint.
460 detaint_natural($group_id);
461 if ($q->shared_with_group) {
462 $sth_update_ngm->execute($group_id, $q->id);
464 else {
465 $sth_insert_ngm->execute($q->id, $group_id);
468 # If we're sharing our query with a group we can bless, we
469 # have the ability to add link to our search to the footer of
470 # direct group members automatically.
471 if ($user->can_bless($group_id) && $cgi->param('force_' . $q->id)) {
472 my $group = new Bugzilla::Group($group_id);
473 my $members = $group->members_non_inherited;
474 foreach my $member (@$members) {
475 next if $member->id == $user->id;
476 $sth_insert_nl->execute($q->id, $member->id)
477 if !$q->link_in_footer($member);
481 else {
482 # They have unshared that query.
483 if ($q->shared_with_group) {
484 $sth_delete_ngm->execute($q->id);
487 # Don't remove namedqueries_link_in_footer entries for users
488 # subscribing to the shared query. The idea is that they will
489 # probably want to be subscribers again should the sharing
490 # user choose to share the query again.
494 $user->flush_queries_cache;
496 # Update profiles.mybugslink.
497 my $showmybugslink = defined($cgi->param("showmybugslink")) ? 1 : 0;
498 $dbh->do("UPDATE profiles SET mybugslink = ? WHERE userid = ?",
499 undef, ($showmybugslink, $user->id));
500 $user->{'showmybugslink'} = $showmybugslink;
504 ###############################################################################
505 # Live code (not subroutine definitions) starts here
506 ###############################################################################
508 my $cgi = Bugzilla->cgi;
510 # This script needs direct access to the username and password CGI variables,
511 # so we save them before their removal in Bugzilla->login, and delete them
512 # before login in case we might be in a sudo session.
513 my $bugzilla_login = $cgi->param('Bugzilla_login');
514 my $bugzilla_password = $cgi->param('Bugzilla_password');
515 $cgi->delete('Bugzilla_login', 'Bugzilla_password') if ($cgi->cookie('sudo'));
517 Bugzilla->login(LOGIN_REQUIRED);
518 $cgi->param('Bugzilla_login', $bugzilla_login);
519 $cgi->param('Bugzilla_password', $bugzilla_password);
521 $vars->{'changes_saved'} = $cgi->param('dosave');
523 my $current_tab_name = $cgi->param('tab') || "settings";
525 # The SWITCH below makes sure that this is valid
526 trick_taint($current_tab_name);
528 $vars->{'current_tab_name'} = $current_tab_name;
530 my $token = $cgi->param('token');
531 check_token_data($token, 'edit_user_prefs') if $cgi->param('dosave');
533 # Do any saving, and then display the current tab.
534 SWITCH: for ($current_tab_name) {
535 /^account$/ && do {
536 SaveAccount() if $cgi->param('dosave');
537 DoAccount();
538 last SWITCH;
540 /^settings$/ && do {
541 SaveSettings() if $cgi->param('dosave');
542 DoSettings();
543 last SWITCH;
545 /^email$/ && do {
546 SaveEmail() if $cgi->param('dosave');
547 DoEmail();
548 last SWITCH;
550 /^permissions$/ && do {
551 DoPermissions();
552 last SWITCH;
554 /^saved-searches$/ && do {
555 SaveSavedSearches() if $cgi->param('dosave');
556 DoSavedSearches();
557 last SWITCH;
559 ThrowUserError("unknown_tab",
560 { current_tab_name => $current_tab_name });
563 delete_token($token) if $cgi->param('dosave');
564 if ($current_tab_name ne 'permissions') {
565 $vars->{'token'} = issue_session_token('edit_user_prefs');
568 # Generate and return the UI (HTML page) from the appropriate template.
569 print $cgi->header();
570 $template->process("account/prefs/prefs.html.tmpl", $vars)
571 || ThrowTemplateError($template->error());