diff options
author | Byron Jones <glob@mozilla.com> | 2015-03-10 06:07:56 +0100 |
---|---|---|
committer | Byron Jones <glob@mozilla.com> | 2015-03-10 06:07:56 +0100 |
commit | ef96ae157223b3309f7703798b32b0b386b2edff (patch) | |
tree | cd0f3c6f234d40998fa6e85ad32c3cac025b610a | |
parent | 9250c96075fda4a6a11b0f09e42423c650debcec (diff) | |
download | bugzilla-ef96ae157223b3309f7703798b32b0b386b2edff.tar.gz bugzilla-ef96ae157223b3309f7703798b32b0b386b2edff.tar.xz |
Bug 1003701: add the ability for users to prevent review/feedback/needinfo requests
13 files changed, 312 insertions, 93 deletions
diff --git a/extensions/Needinfo/Extension.pm b/extensions/Needinfo/Extension.pm index 26bca649e..c03c251b4 100644 --- a/extensions/Needinfo/Extension.pm +++ b/extensions/Needinfo/Extension.pm @@ -14,9 +14,18 @@ use Bugzilla::Error; use Bugzilla::Flag; use Bugzilla::FlagType; use Bugzilla::User; +use Bugzilla::User::Setting; our $VERSION = '0.01'; +BEGIN { + *Bugzilla::User::needinfo_blocked = \&_user_needinfo_blocked; +} + +sub _user_needinfo_blocked { + return $_[0]->settings->{block_needinfo}->{value} eq 'on'; +} + sub install_update_db { my ($self, $args) = @_; my $dbh = Bugzilla->dbh; @@ -47,6 +56,11 @@ sub install_update_db { }); } +sub install_before_final_checks { + my ($self, $args) = @_; + add_setting('block_needinfo', ['on', 'off'], 'off'); +} + # Clear the needinfo? flag if comment is being given by # requestee or someone used the override flag. sub bug_start_of_update { @@ -142,6 +156,7 @@ sub bug_start_of_update { foreach my $requestee (keys %requestees) { my $needinfo_flag = { type_id => $type->id, status => '?' }; if ($requestee ne 'anyone') { + _check_requestee($requestee); $needinfo_flag->{requestee} = $requestee; } push(@new_flags, $needinfo_flag); @@ -166,6 +181,34 @@ sub bug_start_of_update { } } +sub _check_requestee { + my ($requestee) = @_; + my $user = ref($requestee) + ? $requestee + : Bugzilla::User->new({ name => $requestee, cache => 1 }); + if ($user->needinfo_blocked) { + ThrowUserError('needinfo_blocked', { user => $user }); + } +} + +sub object_end_of_create { + my ($self, $args) = @_; + my $object = $args->{object}; + return unless $object->isa('Bugzilla::Flag') + && $object->type->name eq 'needinfo' + && $object->requestee; + _check_requestee($object->requestee); +} + +sub object_end_of_update { + my ($self, $args) = @_; + my $object = $args->{object}; + return unless $object->isa('Bugzilla::Flag') + && $object->type->name eq 'needinfo' + && $object->requestee; + _check_requestee($object->requestee); +} + sub object_before_delete { my ($self, $args) = @_; my $object = $args->{object}; @@ -183,4 +226,22 @@ sub object_before_delete { } } +sub user_preferences { + my ($self, $args) = @_; + return unless + $args->{current_tab} eq 'account' + && $args->{save_changes}; + + my $input = Bugzilla->input_params; + my $dbh = Bugzilla->dbh; + my $settings = Bugzilla->user->settings; + + $dbh->bz_start_transaction(); + my $value = $input->{block_needinfo} ? 'on' : 'off'; + my $setting = Bugzilla::User::Setting->new('block_needinfo'); + $setting->validate_value($value); + $settings->{'block_needinfo'}->set($value); + $dbh->bz_commit_transaction(); +} + __PACKAGE__->NAME; diff --git a/extensions/Needinfo/template/en/default/bug/needinfo.html.tmpl b/extensions/Needinfo/template/en/default/bug/needinfo.html.tmpl index 5edd70f72..7e32509bf 100644 --- a/extensions/Needinfo/template/en/default/bug/needinfo.html.tmpl +++ b/extensions/Needinfo/template/en/default/bug/needinfo.html.tmpl @@ -6,22 +6,24 @@ # defined by the Mozilla Public License, v. 2.0. #%] -[% needinfo_flagtype = "" %] -[% needinfo_flags = [] %] +[% + needinfo_flagtype = ""; + needinfo_flags = []; -[% FOREACH type = bug.flag_types %] - [% IF type.name == 'needinfo' %] - [% needinfo_flagtype = type %] - [% FOREACH flag = type.flags %] - [% IF flag.status == '?' %] - [% needinfo_flags.push(flag) %] - [% END %] - [% END %] - [% LAST IF needinfo_flagtype %] - [% END %] -[% END %] + FOREACH type = bug.flag_types; + IF type.name == 'needinfo'; + needinfo_flagtype = type; + FOREACH flag = type.flags; + IF flag.status == '?'; + needinfo_flags.push(flag); + END; + END; + LAST IF needinfo_flagtype; + END; + END; + + available_mentors = bug.mentors( exclude_needinfo_blocked => 1 ); -[% BLOCK needinfo_comment_div; match = needinfo_flags.last.creation_date.match('^(\d{4})\.(\d{2})\.(\d{2})(.+)$'); date = "$match.0-$match.1-$match.2$match.3"; @@ -131,9 +133,9 @@ identity = '[% bug.qa_contact.realname || bug.qa_contact.login FILTER html FILTER js %]'; } else if (role == 'user') { identity = '[% user.realname || user.login FILTER html FILTER js %]'; - [% FOREACH mentor = bug.mentors %] + [% FOREACH mentor = available_mentors %] } else if (role == '[% mentor.login FILTER js %]') { - identity = '[% mentor.realname || mentor.login FILTER html FILTER js +%] [%+ IF bug.mentors.size > 1 %](mentor)[% END %]'; + identity = '[% mentor.realname || mentor.login FILTER html FILTER js +%] [%+ IF available_mentors.size > 1 %](mentor)[% END %]'; [% END %] } YAHOO.util.Dom.get('needinfo_role_identity').innerHTML = identity; @@ -167,15 +169,21 @@ <label for="needinfo">Need more information from</label> <select name="needinfo_role" id="needinfo_role" onchange="needinfo_role_changed()"> <option value="other">other</option> - <option value="reporter">reporter</option> - <option value="assigned_to">assignee</option> - [% IF Param('useqacontact') && bug.qa_contact.login != "" %] + [% IF NOT bug.reporter.needinfo_blocked %] + <option value="reporter">reporter</option> + [% END %] + [% IF NOT bug.assigned_to.needinfo_blocked %] + <option value="assigned_to">assignee</option> + [% END %] + [% IF Param('useqacontact') && bug.qa_contact.login != "" && !bug.qa_contact.needinfo_blocked %] <option value="qa_contact">qa contact</option> [% END %] - <option value="user">myself</option> - [% FOREACH mentor = bug.mentors %] - <option [% IF bug.mentors.size > 1 %]title="mentor"[% END %] value="[% mentor.login FILTER html %]"> - [% bug.mentors.size == 1 ? "mentor" : mentor.login FILTER html %] + [% IF NOT user.needinfo_blocked %] + <option value="user">myself</option> + [% END %] + [% FOREACH mentor = available_mentors %] + <option [% IF available_mentors.size > 1 %]title="mentor"[% END %] value="[% mentor.login FILTER html %]"> + [% available_mentors.size == 1 ? "mentor" : mentor.login FILTER html %] </option> [% END %] </select> diff --git a/extensions/Needinfo/template/en/default/hook/account/prefs/account-field.html.tmpl b/extensions/Needinfo/template/en/default/hook/account/prefs/account-field.html.tmpl new file mode 100644 index 000000000..4e0253610 --- /dev/null +++ b/extensions/Needinfo/template/en/default/hook/account/prefs/account-field.html.tmpl @@ -0,0 +1,25 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +<tr> + [%# this section is shared by both the needinfo and review extensions %] + [%# only show the label once %] + [% IF request_blocked_header %] + <td></td> + [% ELSE %] + [% request_blocked_header = 1 %] + <th align="right" valign="top">Request blocking:</th> + [% END %] + <td> + <input type="checkbox" id="block_needinfo" name="block_needinfo" value="1" + [% " checked" IF user.settings.block_needinfo.value == "on" %]> + <label for="block_needinfo"> + Block needinfo requests + </label> + </td> +</tr> diff --git a/extensions/Needinfo/template/en/default/hook/global/setting-descs-settings.none.tmpl b/extensions/Needinfo/template/en/default/hook/global/setting-descs-settings.none.tmpl new file mode 100644 index 000000000..ec435e58b --- /dev/null +++ b/extensions/Needinfo/template/en/default/hook/global/setting-descs-settings.none.tmpl @@ -0,0 +1,11 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% + setting_descs.block_needinfo = "Block needinfo requests" +%] diff --git a/extensions/Needinfo/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Needinfo/template/en/default/hook/global/user-error-errors.html.tmpl index f1241bc61..42d47e928 100644 --- a/extensions/Needinfo/template/en/default/hook/global/user-error-errors.html.tmpl +++ b/extensions/Needinfo/template/en/default/hook/global/user-error-errors.html.tmpl @@ -10,4 +10,8 @@ [% title = 'Needinfo Illegal Change' %] Only the requestee or a user with the required permissions can clear a needinfo flag. +[% ELSIF error == "needinfo_blocked" %] + [% title = "Needinfo Request Blocked" %] + [% user.identity FILTER html %] is not currently accepting "needinfo" + requests. [% END %] diff --git a/extensions/Review/Extension.pm b/extensions/Review/Extension.pm index 9eb831071..ab15e8a7b 100644 --- a/extensions/Review/Extension.pm +++ b/extensions/Review/Extension.pm @@ -20,7 +20,8 @@ use Bugzilla::Extension::Review::Util; use Bugzilla::Install::Filesystem; use Bugzilla::Search; use Bugzilla::User; -use Bugzilla::Util qw(clean_text diff_arrays); +use Bugzilla::User::Setting; +use Bugzilla::Util qw(clean_text); use constant UNAVAILABLE_RE => qr/\b(?:unavailable|pto|away)\b/i; @@ -40,14 +41,15 @@ BEGIN { *Bugzilla::Bug::is_mentor = \&_bug_is_mentor; *Bugzilla::Bug::set_bug_mentors = \&_bug_set_bug_mentors; *Bugzilla::User::review_count = \&_user_review_count; + *Bugzilla::User::reviews_blocked = \&_user_reviews_blocked; } # # monkey-patched methods # -sub _product_reviewers { _reviewers($_[0], 'product', $_[1]) } -sub _product_reviewers_objs { _reviewers_objs($_[0], 'product', $_[1]) } +sub _product_reviewers { _reviewers($_[0], 'product', $_[1]) } +sub _product_reviewers_objs { _reviewers_objs($_[0], 'product', $_[1]) } sub _component_reviewers { _reviewers($_[0], 'component', $_[1]) } sub _component_reviewers_objs { _reviewers_objs($_[0], 'component', $_[1]) } @@ -71,8 +73,11 @@ sub _reviewers_objs { my %user_map = map { $_->id => $_ } @$users; my @reviewers = map { $user_map{$_} } @$user_ids; if (!$include_disabled) { - @reviewers = grep { $_->is_enabled - && $_->name !~ UNAVAILABLE_RE } @reviewers; + @reviewers = grep { + $_->is_enabled + && $_->name !~ UNAVAILABLE_RE + && !$_->reviews_blocked + } @reviewers; } $object->{reviewers} = \@reviewers; } @@ -96,12 +101,17 @@ sub _user_review_count { return $self->{review_count}; } +sub _user_reviews_blocked { + return $_[0]->settings->{block_reviews}->{value} eq 'on'; +} + # # mentor # sub _bug_mentors { - my ($self) = @_; + my ($self, $options) = @_; + $options //= {}; my $dbh = Bugzilla->dbh; if (!$self->{bug_mentors}) { my $mentor_ids = $dbh->selectcol_arrayref(" @@ -110,14 +120,20 @@ sub _bug_mentors { $self->id); $self->{bug_mentors} = []; foreach my $mentor_id (@$mentor_ids) { - push(@{ $self->{bug_mentors} }, - Bugzilla::User->new({ id => $mentor_id, cache => 1 })); + push(@{ $self->{bug_mentors} }, Bugzilla::User->new({ id => $mentor_id, cache => 1 })); } $self->{bug_mentors} = [ sort { $a->login cmp $b->login } @{ $self->{bug_mentors} } ]; } - return $self->{bug_mentors}; + my @result = @{ $self->{bug_mentors} }; + if ($options->{exclude_needinfo_blocked}) { + @result = grep { !$_->needinfo_blocked } @result; + } + if ($options->{exclude_review_blocked}) { + @result = grep { !$_->reviews_blocked } @result; + } + return \@result; } sub _bug_is_mentor { @@ -322,6 +338,7 @@ sub object_end_of_create { }); } elsif (_is_countable_flag($object) && $object->requestee_id && $object->status eq '?') { + _check_requestee($object); _adjust_request_count($object, +1); } if (_is_countable_flag($object)) { @@ -373,6 +390,9 @@ sub object_end_of_update { if ($old_status ne '?' && $new_status eq '?') { # setting flag to ? _adjust_request_count($object, +1); + if ($object->requestee_id) { + _check_requestee($object); + } } elsif ($old_status eq '?' && $new_status ne '?') { # setting flag from ? @@ -384,12 +404,14 @@ sub object_end_of_update { } elsif (!$old_object->requestee_id && $object->requestee_id) { # setting requestee + _check_requestee($object); _adjust_request_count($object, +1); } elsif ($old_object->requestee_id && $object->requestee_id && $old_object->requestee_id != $object->requestee_id) { # changing requestee + _check_requestee($object); _adjust_request_count($old_object, -1); _adjust_request_count($object, +1); } @@ -429,6 +451,15 @@ sub _is_countable_flag { return $type_name eq 'review' || $type_name eq 'feedback' || $type_name eq 'needinfo'; } +sub _check_requestee { + my ($flag) = @_; + return unless $flag->type->name eq 'review' || $flag->type->name eq 'feedback'; + if ($flag->requestee->reviews_blocked) { + ThrowUserError('reviews_blocked', + { user => $flag->requestee, flagtype => $flag->type->name }); + } +} + sub _log_flag_state_activity { my ($self, $flag, $status, $timestamp, $setter_id) = @_; @@ -601,6 +632,24 @@ sub webservice { $dispatch->{Review} = "Bugzilla::Extension::Review::WebService"; } +sub user_preferences { + my ($self, $args) = @_; + return unless + $args->{current_tab} eq 'account' + && $args->{save_changes}; + + my $input = Bugzilla->input_params; + my $dbh = Bugzilla->dbh; + my $settings = Bugzilla->user->settings; + + $dbh->bz_start_transaction(); + my $value = $input->{block_reviews} ? 'on' : 'off'; + my $setting = Bugzilla::User::Setting->new('block_reviews'); + $setting->validate_value($value); + $settings->{'block_reviews'}->set($value); + $dbh->bz_commit_transaction(); +} + sub page_before_template { my ($self, $args) = @_; @@ -945,5 +994,9 @@ sub install_filesystem { }; } +sub install_before_final_checks { + my ($self, $args) = @_; + add_setting('block_reviews', ['on', 'off'], 'off'); +} __PACKAGE__->NAME; diff --git a/extensions/Review/template/en/default/hook/account/prefs/account-field.html.tmpl b/extensions/Review/template/en/default/hook/account/prefs/account-field.html.tmpl new file mode 100644 index 000000000..baf3a6b94 --- /dev/null +++ b/extensions/Review/template/en/default/hook/account/prefs/account-field.html.tmpl @@ -0,0 +1,26 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +<tr> + [%# this section is shared by both the needinfo and review extensions %] + [%# only show the label once %] + [% IF request_blocked_header %] + <td></td> + [% ELSE %] + [% request_blocked_header = 1 %] + <th align="right" valign="top">Request blocking:</th> + [% END %] + <td> + <input type="checkbox" id="block_reviews" name="block_reviews" value="1" + [% " checked" IF user.settings.block_reviews.value == "on" %] + > + <label for="block_reviews"> + Block review and feedback requests + </label> + </td> +</tr> diff --git a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl index ff166ac4c..a1ced8108 100644 --- a/extensions/Review/template/en/default/hook/global/header-start.html.tmpl +++ b/extensions/Review/template/en/default/hook/global/header-start.html.tmpl @@ -24,12 +24,12 @@ [% IF bug %] [%# create attachment %] - [% mentors = bug.mentors %] + [% mentors = bug.mentors( exclude_review_blocked => 1 ) %] [% product_obj = bug.product_obj %] [% component_obj = bug.component_obj %] [% ELSIF attachment.bug %] [%# edit attachment %] - [% mentors = attachment.bug.mentors %] + [% mentors = attachment.bug.mentors( exclude_review_blocked => 1 ) %] [% product_obj = attachment.bug.product_obj %] [% component_obj = attachment.bug.component_obj %] [% ELSE %] diff --git a/extensions/Review/template/en/default/hook/global/setting-descs-settings.none.tmpl b/extensions/Review/template/en/default/hook/global/setting-descs-settings.none.tmpl new file mode 100644 index 000000000..f265aed10 --- /dev/null +++ b/extensions/Review/template/en/default/hook/global/setting-descs-settings.none.tmpl @@ -0,0 +1,11 @@ +[%# This Source Code Form is subject to the terms of the Mozilla Public + # License, v. 2.0. If a copy of the MPL was not distributed with this + # file, You can obtain one at http://mozilla.org/MPL/2.0/. + # + # This Source Code Form is "Incompatible With Secondary Licenses", as + # defined by the Mozilla Public License, v. 2.0. + #%] + +[% + setting_descs.block_reviews = "Block review and feedback requests" +%] diff --git a/extensions/Review/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/Review/template/en/default/hook/global/user-error-errors.html.tmpl index ca143cca3..4fcd47a68 100644 --- a/extensions/Review/template/en/default/hook/global/user-error-errors.html.tmpl +++ b/extensions/Review/template/en/default/hook/global/user-error-errors.html.tmpl @@ -23,4 +23,9 @@ [% title = "Parameters Required" %] You may not search flag state activity without any search terms. +[% ELSIF error == "reviews_blocked" %] + [% title = "Request Blocked" %] + [% user.identity FILTER html %] is not currently accepting + '[% flagtype FILTER html %]' requests. + [% END %] diff --git a/extensions/UserProfile/template/en/default/hook/account/prefs/account-field.html.tmpl b/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl index f2e3aad01..f2e3aad01 100644 --- a/extensions/UserProfile/template/en/default/hook/account/prefs/account-field.html.tmpl +++ b/extensions/UserProfile/template/en/default/hook/account/prefs/account-start.html.tmpl diff --git a/template/en/default/account/prefs/account.html.tmpl b/template/en/default/account/prefs/account.html.tmpl index 95819e451..0c8f0807f 100644 --- a/template/en/default/account/prefs/account.html.tmpl +++ b/template/en/default/account/prefs/account.html.tmpl @@ -24,83 +24,98 @@ # new_login_name: string. The user's new Bugzilla login whilst not confirmed. (optional) #%] +[%# BMO - add hook for displaying user-profile link %] +[% Hook.process('start') %] + <table> <tr> - <td colspan="3"> - Please enter your existing password to confirm account changes. - </td> - </tr> - <tr> - <th align="right">Password:</th> + <th align="right">Your real name:</th> <td> - <input type="hidden" name="old_login" value="[% user.login FILTER html %]"> - <input type="password" name="old_password"> + <input size="35" name="realname" value="[% realname FILTER html %]"> + <i>optional, but encouraged</i> </td> </tr> - <tr> - <td colspan="2"><hr></td> - </tr> + + [%# BMO - moved field hook from end of file to here to group with other account fields %] + [% Hook.process('field') %] + + [% SET can_change = [] %] [% IF user.authorizer.can_change_password %] + [% can_change.push('password') %] + [% END %] + [% IF user.authorizer.can_change_email && Param('allowemailchange') %] + [% can_change.push('email address') %] + [% END %] + + [% IF can_change.size %] <tr> - <th align="right">New password:</th> - <td> - <input type="password" name="new_password1"> + <td></td> + <td><hr></td> + </tr> + <tr> + <td colspan="3"> + Your current password is required to + confirm [% can_change.join(' or ') FILTER html %] + changes. </td> </tr> - <tr> - <th align="right">Confirm new password:</th> + <th align="right">Current password:</th> <td> - <input type="password" name="new_password2"> + <input type="hidden" name="old_login" value="[% user.login FILTER html %]"> + <input type="password" name="old_password"> </td> </tr> - [% END %] + [% IF user.authorizer.can_change_password %] + <tr> + <th align="right">New password:</th> + <td> + <input type="password" name="new_password1"> + </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> + <tr> + <th align="right">Confirm new password:</th> + <td> + <input type="password" name="new_password2"> + </td> + </tr> + [% END %] - [% IF user.authorizer.can_change_email && Param('allowemailchange') %] - [% IF login_change_date %] - [% IF new_login_name %] - <tr> - <th align="right">Pending email address:</th> - <td>[% new_login_name FILTER html %]</td> - </tr> - <tr> - <th align="right">Change request expires:</th> - <td>[% login_change_date FILTER time %]</td> - </tr> + [% IF user.authorizer.can_change_email && Param('allowemailchange') %] + [% IF login_change_date %] + [% IF new_login_name %] + <tr> + <th align="right">Pending email address:</th> + <td>[% new_login_name FILTER html %]</td> + </tr> + <tr> + <th align="right">Change request expires:</th> + <td>[% login_change_date FILTER time %]</td> + </tr> + [% ELSE %] + <tr> + <th align="right">Confirmed email address:</th> + <td>[% user.login FILTER html %]</td> + </tr> + <tr> + <th align="right">Completion date:</th> + <td>[% login_change_date FILTER time %]</td> + </tr> + [% END %] [% ELSE %] <tr> - <th align="right">Confirmed email address:</th> - <td>[% user.login FILTER html %]</td> - </tr> - <tr> - <th align="right">Completion date:</th> - <td>[% login_change_date FILTER time %]</td> + <th align="right">New email address:</th> + <td> + <input size="35" name="new_login_name"> + </td> </tr> [% END %] - [% ELSE %] - <tr> - <th> - [% IF Param('emailsuffix') %] - New login: - [% ELSE %] - New email address: - [% END %] - </th> - <td> - <input size="35" id="new_login_name" name="new_login_name" - [%- ' type="email"' UNLESS Param('emailsuffix') %]> - </td> - </tr> [% END %] + <tr> + <td></td> + <td><hr></td> + </tr> [% END %] - [% Hook.process('field') %] - </table> diff --git a/template/en/default/account/prefs/email.html.tmpl b/template/en/default/account/prefs/email.html.tmpl index ffb153785..80185b503 100644 --- a/template/en/default/account/prefs/email.html.tmpl +++ b/template/en/default/account/prefs/email.html.tmpl @@ -60,8 +60,8 @@ function SetCheckboxes(setting) { } } -document.write('<input type="button" value="Enable All Mail" onclick="SetCheckboxes(true); return false;">\n'); -document.write('<input type="button" value="Disable All Mail" onclick="SetCheckboxes(false); return false;">\n'); +document.write('<input type="button" value="Enable All Bugmail" onclick="SetCheckboxes(true); return false;">\n'); +document.write('<input type="button" value="Disable All Bugmail" onclick="SetCheckboxes(false); return false;">\n'); // --> </script> |