From b08f5b4abf3a368cbd2969cd67ab4425806c87d9 Mon Sep 17 00:00:00 2001 From: Dave Miller Date: Wed, 1 Jul 2026 02:15:38 -0400 Subject: [PATCH 01/12] Bug 1983391: Add a donation banner to the home page --- Bugzilla/Config/Donation.pm | 36 +++++ Bugzilla/Donation.pm | 129 ++++++++++++++++ Bugzilla/Install.pm | 18 +++ Bugzilla/Install/DB.pm | 8 +- Bugzilla/User/Setting/Donation.pm | 51 +++++++ index.cgi | 18 +++ skins/standard/global.css | 141 ++++++++++++++++++ .../en/default/account/prefs/donate.html.tmpl | 46 ++++++ .../en/default/account/prefs/prefs.html.tmpl | 13 ++ .../default/admin/params/donation.html.tmpl | 20 +++ template/en/default/index.html.tmpl | 43 +++++- userprefs.cgi | 34 +++++ 12 files changed, 555 insertions(+), 2 deletions(-) create mode 100644 Bugzilla/Config/Donation.pm create mode 100644 Bugzilla/Donation.pm create mode 100644 Bugzilla/User/Setting/Donation.pm create mode 100644 template/en/default/account/prefs/donate.html.tmpl create mode 100644 template/en/default/admin/params/donation.html.tmpl diff --git a/Bugzilla/Config/Donation.pm b/Bugzilla/Config/Donation.pm new file mode 100644 index 0000000000..cb674940da --- /dev/null +++ b/Bugzilla/Config/Donation.pm @@ -0,0 +1,36 @@ +# 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. + +package Bugzilla::Config::Donation; + +use 5.10.1; +use strict; +use warnings; + +use Bugzilla::Config::Common; + +our $sortkey = 175; + +use constant get_param_list => ( + { + name => 'donation_banner_visibility', + type => 's', + choices => ['admins_only', 'end_users', 'disabled'], + default => 'admins_only', + checker => \&check_multi, + }, +); + +1; + +__END__ + +=head1 NAME + +Bugzilla::Config::Donation - Donation banner settings + +=cut diff --git a/Bugzilla/Donation.pm b/Bugzilla/Donation.pm new file mode 100644 index 0000000000..c062b4a673 --- /dev/null +++ b/Bugzilla/Donation.pm @@ -0,0 +1,129 @@ +# 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. + +package Bugzilla::Donation; + +use 5.10.1; +use strict; +use warnings; + +use Bugzilla::Constants; +use Bugzilla::Token qw(issue_session_token); + +use DateTime; + +use constant BANNER_URL => 'https://bugzilla.org/donate'; +use constant BANNER_MESSAGES => ( + 'Help us make Bugzilla better more often.', + 'A small donation helps keep Bugzilla moving forward.', + 'Support the people who keep Bugzilla running.', + 'Even a little funding helps Bugzilla improve more quickly.', + 'If Bugzilla helps your team, consider helping Bugzilla too.', +); + +sub get_banner { + my $user = Bugzilla->user; + return undef if !$user->id; + + my $visibility = Bugzilla->params->{'donation_banner_visibility'} || 'admins_only'; + return undef if $visibility eq 'disabled'; + return undef if $visibility eq 'admins_only' && !$user->in_group('admin'); + + my $settings = $user->settings; + my $pref = $settings->{'donate_banner_pref'}->{'value'}; + my $last_version = $settings->{'donate_banner_last_version'}->{'value'} || '0.0'; + my $next_date = $settings->{'donate_banner_reminder_date'}->{'value'} || '1970-01-01'; + my $current_version = BUGZILLA_VERSION; + + my $show; + my $show_thanks = 0; + if ($pref eq 'next_upgrade') { + $show = ($last_version ne $current_version); + $show_thanks = $show + && $user->in_group('admin') + && $last_version ne '0' + && $last_version ne '0.0'; + } + elsif ($pref eq 'specific_date') { + my $today = DateTime->now(time_zone => Bugzilla->local_timezone)->ymd; + $show = ($next_date le $today); + } + else { + $show = 0; + } + + return undef if !$show; + + my @messages = BANNER_MESSAGES; + my $message = $messages[int(rand(@messages))]; + + my $data = { + url => BANNER_URL, + message => $message, + show_thanks => $show_thanks, + visibility => $visibility, + settings_link => 'editparams.cgi?section=donation#donation_banner_visibility_desc', + token => issue_session_token('edit_user_prefs'), + }; + + if ($visibility eq 'admins_only') { + $data->{'visibility_note'} + = 'This message is only shown to logged-in users with admin privs.'; + } + elsif ($user->in_group('admin')) { + $data->{'visibility_note'} + = 'This message is visible to all logged-in users.'; + } + + return $data; +} + +sub set_banner_preference { + my ($action) = @_; + my $user = Bugzilla->user; + my $settings = $user->settings; + + my $pref_setting = $settings->{'donate_banner_pref'}; + my $date_setting = $settings->{'donate_banner_reminder_date'}; + my $version_setting = $settings->{'donate_banner_last_version'}; + my $current_version = BUGZILLA_VERSION; + + if ($action eq 'next_upgrade') { + $pref_setting->set('next_upgrade'); + $version_setting->set($current_version); + return 'index.cgi'; + } + if ($action eq 'week' || $action eq 'month') { + my $days = $action eq 'week' ? 7 : 30; + my $dt = DateTime->now(time_zone => Bugzilla->local_timezone); + $dt->add(days => $days); + $pref_setting->set('specific_date'); + $date_setting->set($dt->ymd); + $version_setting->set($current_version); + return 'index.cgi'; + } + if ($action eq 'never') { + $pref_setting->set('never'); + $version_setting->set($current_version); + return 'index.cgi'; + } + if ($action eq 'date') { + return 'userprefs.cgi?tab=donate'; + } + + return 'index.cgi'; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::Donation - Donation banner helpers + +=cut diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm index c982c072ed..df41b331ce 100644 --- a/Bugzilla/Install.pm +++ b/Bugzilla/Install.pm @@ -58,6 +58,24 @@ sub SETTINGS { category => 'Searching' }, + # 2026-07-01 bugzilla@mozilla.org -- Bug 1983391 + { + name => 'donate_banner_pref', + options => ['next_upgrade', 'specific_date', 'never'], + default => 'next_upgrade', + category => 'Bugzilla.org', + }, + { + name => 'donate_banner_last_version', + default => '0.0', + category => 'Bugzilla.org', + }, + { + name => 'donate_banner_reminder_date', + default => '1970-01-01', + category => 'Bugzilla.org', + }, + # 2005-03-10 travis@sedsystems.ca -- Bug 199048 { name => 'comment_sort_order', diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 0265b33507..2227ca1712 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -3076,7 +3076,13 @@ sub _rederive_regex_groups { $sth->execute(GRANT_REGEXP); while (my ($uid, $login, $gid, $rexp, $present) = $sth->fetchrow_array()) { - if ($login =~ m/$rexp/i) { + my $matches = eval { $login =~ m/$rexp/i ? 1 : 0 }; + if ($@) { + print "Skipping invalid group regexp for group $gid: $rexp\n"; + next; + } + + if ($matches) { $sth_add->execute($uid, $gid) unless $present; } else { diff --git a/Bugzilla/User/Setting/Donation.pm b/Bugzilla/User/Setting/Donation.pm new file mode 100644 index 0000000000..e67a2f3661 --- /dev/null +++ b/Bugzilla/User/Setting/Donation.pm @@ -0,0 +1,51 @@ +# 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. + +package Bugzilla::User::Setting::Donation; + +use 5.10.1; +use strict; +use warnings; + +use base qw(Bugzilla::User::Setting); + +use Bugzilla::Error; +use Bugzilla::Util qw(trick_taint validate_date); + +sub validate_value { + my ($self, $value) = @_; + my $name = $self->{'_setting_name'}; + + if ($name eq 'donate_banner_pref') { + return $self->SUPER::validate_value($value); + } + + if ($name eq 'donate_banner_last_version') { + if ($value =~ /^0$|^[0-9A-Za-z][0-9A-Za-z._+-]*$/) { + trick_taint($value); + return; + } + } + elsif ($name eq 'donate_banner_reminder_date') { + if ($value eq '1970-01-01' || validate_date($value)) { + trick_taint($value); + return; + } + } + + ThrowCodeError('setting_value_invalid', {name => $name, value => $value}); +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::User::Setting::Donation - Donation banner user settings + +=cut diff --git a/index.cgi b/index.cgi index 82fa5ede97..dee6e3dbd6 100755 --- a/index.cgi +++ b/index.cgi @@ -15,6 +15,8 @@ use lib qw(. lib local/lib/perl5); use Bugzilla; use Bugzilla::Constants; use Bugzilla::Error; +use Bugzilla::Token; +use Bugzilla::Donation; use Bugzilla::Update; use Digest::MD5 qw(md5_hex); use List::MoreUtils qw(any); @@ -44,6 +46,20 @@ if ($cgi->param('logout')) { $cgi->delete('logout'); } +my $donate_action = $cgi->param('donate_action'); +if ($donate_action) { + Bugzilla->login(LOGIN_REQUIRED) unless Bugzilla->user->id; + + my $token = $cgi->param('token'); + check_token_data($token, 'edit_user_prefs'); + + my $redirect = Bugzilla::Donation::set_banner_preference($donate_action); + delete_token($token); + + print $cgi->redirect(-uri => $redirect); + exit; +} + # our weak etag is based on the Bugzilla version parameter (BMO customization) and the announcehtml # if either change, the cache will be considered invalid. my @etag_parts = ( @@ -90,6 +106,8 @@ else { $vars->{'release'} = Bugzilla::Update::get_notifications(); } + $vars->{'donation'} = Bugzilla::Donation::get_banner(); + # Generate and return the UI (HTML page) from the appropriate template. $template->process("index.html.tmpl", $vars) or ThrowTemplateError($template->error()); diff --git a/skins/standard/global.css b/skins/standard/global.css index 973cf85df7..9080f58cc7 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -1050,6 +1050,147 @@ input[type="radio"]:checked { color: var(--secondary-label-color); } +#new_release.donation_banner { + border: 2px solid rgba(var(--accent-color-green-1), 0.75); + color: var(--primary-text-color); + background: var(--primary-region-background-color); + box-shadow: var(--primary-region-box-shadow); + border-radius: 8px; + font-weight: normal; +} + +.donation_banner_layout { + display: flex; + align-items: center; + gap: 1rem; +} + +.donation_banner_image { + width: 110px; + height: 110px; + flex: 0 0 auto; + border: 1px dashed rgba(var(--accent-color-green-1), 0.7); + border-radius: 10px; + background: rgba(var(--accent-color-green-1), 0.08); +} + +.donation_banner_content { + flex: 1 1 auto; + padding-top: 0.35rem; +} + +.donation_banner_thanks { + margin: 0 0 0.5rem; + color: var(--primary-text-color); + font-weight: bold; +} + +.donation_banner_message { + margin: 0 0 0.6rem; + color: var(--primary-text-color); + font-size: 1.08rem; + line-height: 1.45; +} + +.donation_banner_cta_row { + margin: 0 0 0.75rem; +} + +.donation_banner_cta { + display: inline-block; + padding: 0.55rem 1rem; + border: 1px solid rgb(var(--accent-color-green-1)); + border-radius: 5px; + background: rgb(var(--accent-color-green-1)); + color: #fff !important; + text-decoration: none; + font-weight: bold; + font-size: 1rem; +} + +.donation_banner_cta:hover { + border-color: rgb(var(--accent-color-green-1)); + background: rgba(var(--accent-color-green-1), 0.9); + text-decoration: none; +} + +.donation_banner_form { + display: flex; + align-items: center; + flex-wrap: wrap; + gap: 0.5rem; +} + +.donation_banner_label { + font-weight: bold; +} + +.donation_banner_select { + min-width: 240px; +} + +.donation_banner_submit { + padding: 0.35rem 0.8rem; +} + +.donation_banner_submit:hover { + border-color: var(--hovered-secondary-button-border-color); + color: var(--hovered-secondary-button-foreground-color); + background: var(--hovered-secondary-button-background-color); +} + +.donation_banner_form_subtle { + font-size: 0.9rem; + opacity: 0.9; +} + +.donation_banner_form_subtle .donation_banner_label { + font-weight: normal; + color: var(--secondary-label-color); +} + +.donation_banner_form_subtle .donation_banner_select { + min-width: 230px; + padding: 0.25rem 0.4rem; + font-size: 0.9rem; +} + +.donation_banner_form_subtle .donation_banner_submit { + padding: 0.25rem 0.65rem; + font-size: 0.88rem; +} + +@media (max-width: 720px) { + .donation_banner_layout { + align-items: flex-start; + } + + .donation_banner_image { + width: 84px; + height: 84px; + } + + .donation_banner_select { + min-width: 100%; + } +} + +@media screen and (prefers-color-scheme: dark) { + #new_release.donation_banner { + border-color: rgba(var(--accent-color-green-1), 0.6); + background: rgba(40, 46, 43, 0.95); + } + + .donation_banner_image { + border-color: rgba(var(--accent-color-green-1), 0.55); + background: rgba(var(--accent-color-green-1), 0.12); + } + + .donation_banner_form_subtle { + opacity: 1; + } +} + /** * Global header */ diff --git a/template/en/default/account/prefs/donate.html.tmpl b/template/en/default/account/prefs/donate.html.tmpl new file mode 100644 index 0000000000..f31a9529b4 --- /dev/null +++ b/template/en/default/account/prefs/donate.html.tmpl @@ -0,0 +1,46 @@ +[%# 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. + #%] + +[% + donate_pref = user.settings.donate_banner_pref.value; + reminder_date = user.settings.donate_banner_reminder_date.value; +%] + +

+ Choose when you want to be reminded about supporting Bugzilla: +

+ + + + + + + + + + +
+ +
+ +
Used only when "Remind me on a specific date" is selected.
+
diff --git a/template/en/default/account/prefs/prefs.html.tmpl b/template/en/default/account/prefs/prefs.html.tmpl index 6b97b08cfc..c4cfee7cf2 100644 --- a/template/en/default/account/prefs/prefs.html.tmpl +++ b/template/en/default/account/prefs/prefs.html.tmpl @@ -60,6 +60,12 @@ link => "userprefs.cgi?tab=settings", saveable => "1" }, + { + name => "donate", + label => "Donate to Bugzilla", + link => "userprefs.cgi?tab=donate", + saveable => "1" + }, { name => "email", label => "Email Preferences", @@ -98,6 +104,13 @@ saveable => "0" }, ]; + IF Param('donation_banner_visibility') == 'disabled'; + visible_tabs = []; + FOREACH tab IN tabs; + visible_tabs.push(tab) IF tab.name != 'donate'; + END; + tabs = visible_tabs; + END; Hook.process('tabs'); FOREACH tab IN tabs; diff --git a/template/en/default/admin/params/donation.html.tmpl b/template/en/default/admin/params/donation.html.tmpl new file mode 100644 index 0000000000..8bbd200d02 --- /dev/null +++ b/template/en/default/admin/params/donation.html.tmpl @@ -0,0 +1,20 @@ +[%# 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. + #%] +[% + title = "Donation" + desc = "Configure the Bugzilla donation banner" +%] + +[% param_descs = { + donation_banner_visibility => + "Controls which logged-in users can see the donation banner on the " + _ "$terms.Bugzilla home page." + _ " ", +} %] diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl index 5e7098dc76..63f8a7ccc5 100644 --- a/template/en/default/index.html.tmpl +++ b/template/en/default/index.html.tmpl @@ -32,7 +32,48 @@ no_yui = 1 %] -[% IF release %] +[% IF donation %] +
+
+ Buggie holding donation funds placeholder + +
+ [% IF donation.show_thanks %] +

Thanks for upgrading Bugzilla!

+ [% END %] + +

[% donation.message FILTER html %]

+ +

+ Donate to Bugzilla +

+ +
+ + + + +
+ + [% IF donation.visibility_note %] +

[% donation.visibility_note FILTER html %] + You can configure this notification from the + Parameters page.

+ [% END %] +
+
+
+[% ELSIF release %]
[% IF release.data %] [% IF release.eos_date %] diff --git a/userprefs.cgi b/userprefs.cgi index d7a5eb146a..40be65847e 100755 --- a/userprefs.cgi +++ b/userprefs.cgi @@ -245,6 +245,35 @@ sub DoSettings { $vars->{dont_show_button} = !$has_settings_enabled; } +sub DoDonate { + my $user = Bugzilla->user; + + $vars->{settings} = $user->settings; +} + +sub SaveDonate { + my $cgi = Bugzilla->cgi; + my $user = Bugzilla->user; + my $settings = $user->settings; + + my $pref = $cgi->param('donate_banner_pref') || 'next_upgrade'; + my $date = $cgi->param('donate_banner_reminder_date') || ''; + + if ($pref eq 'specific_date') { + validate_date($date) + || ThrowUserError('illegal_date', {date => $date, format => 'YYYY-MM-DD'}); + $settings->{'donate_banner_reminder_date'}->set($date); + } + elsif ($pref ne 'next_upgrade' && $pref ne 'never') { + ThrowCodeError('setting_value_invalid', + {name => 'donate_banner_pref', value => $pref}); + } + + $settings->{'donate_banner_pref'}->set($pref); + clear_settings_cache($user->id); + $vars->{settings} = $user->settings(1); +} + sub SaveSettings { my $cgi = Bugzilla->cgi; my $user = Bugzilla->user; @@ -1037,6 +1066,11 @@ SWITCH: for ($current_tab_name) { DoSettings(); last SWITCH; }; + /^donate$/ && do { + SaveDonate() if $save_changes; + DoDonate(); + last SWITCH; + }; /^email$/ && do { SaveEmail() if $save_changes; DoEmail(); From c2e833d4f2b2997ba31bfab2a637cb74f085bdf2 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 07:47:55 +0100 Subject: [PATCH 02/12] Bug 1983391: Dismiss the next-upgrade donation reminder from the Donate tab SaveDonate() set donate_banner_pref but never stamped donate_banner_last_version, so Bugzilla::Donation::get_banner() still saw last_version ne BUGZILLA_VERSION and kept showing the banner. Choosing 'Remind me after the next upgrade' in the Donate preferences tab therefore did not actually dismiss the banner, unlike the home page banner path (set_banner_preference) which stamps the version. Stamp BUGZILLA_VERSION when saving the donate preferences so both dismiss paths behave consistently. --- userprefs.cgi | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/userprefs.cgi b/userprefs.cgi index 40be65847e..4c6c15d701 100755 --- a/userprefs.cgi +++ b/userprefs.cgi @@ -270,6 +270,12 @@ sub SaveDonate { } $settings->{'donate_banner_pref'}->set($pref); + + # Stamp the current version so that the "next upgrade" reminder is dismissed + # until Bugzilla is actually upgraded, matching the home page banner. Without + # this, get_banner() still sees last_version != current and keeps showing. + $settings->{'donate_banner_last_version'}->set(BUGZILLA_VERSION); + clear_settings_cache($user->id); $vars->{settings} = $user->settings(1); } From f70d3a9864f89b7e180846fac102e9d1457ef629 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 07:48:37 +0100 Subject: [PATCH 03/12] Bug 1983391: Hide internal donation settings from the generic Settings tab The donation banner stores its per-user state as three user settings. donate_banner_last_version and donate_banner_reminder_date have no legal_values, so they rendered as empty dropdowns under a new 'Bugzilla.org' category in Preferences > General Preferences. Worse, because their only submitted value was the '-isdefault' sentinel, saving that tab reset them to their defaults via reset_to_default(), which silently made a previously dismissed banner reappear. donate_banner_pref was also duplicated there alongside its dedicated Donate tab. These settings are managed from the Donate tab and the home page banner, so exclude all three from both the display (DoSettings) and the save loop (SaveSettings) of the generic Settings tab. --- userprefs.cgi | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/userprefs.cgi b/userprefs.cgi index 4c6c15d701..efc00d00ba 100755 --- a/userprefs.cgi +++ b/userprefs.cgi @@ -29,6 +29,14 @@ use DateTime; use constant SESSION_MAX => 20; +# Donation banner settings are managed from the dedicated "Donate" tab (and the +# home page banner), not the generic Settings tab. Two of them hold internal +# state (last shown version, reminder date) with no legal_values, so they would +# otherwise render as empty dropdowns and be reset to their defaults whenever the +# Settings tab is saved. +use constant DONATION_SETTINGS => + qw(donate_banner_pref donate_banner_last_version donate_banner_reminder_date); + local our $template = Bugzilla->template; local our $vars = {}; @@ -230,10 +238,12 @@ sub DoSettings { my $user = Bugzilla->user; my %settings; + my %is_donation_setting = map { $_ => 1 } DONATION_SETTINGS; my $has_settings_enabled = 0; foreach my $name (sort keys %{$user->settings}) { my $setting = $user->settings->{$name}; next if !$setting->{is_enabled}; + next if $is_donation_setting{$name}; my $category = $setting->{category}; $settings{$category} ||= []; push(@{$settings{$category}}, $setting); @@ -288,8 +298,11 @@ sub SaveSettings { my @setting_list = keys %$settings; my $mfa_event = undef; + my %is_donation_setting = map { $_ => 1 } DONATION_SETTINGS; + foreach my $name (@setting_list) { next if !($settings->{$name}->{'is_enabled'}); + next if $is_donation_setting{$name}; my $value = $cgi->param($name); next unless defined $value; my $setting = new Bugzilla::User::Setting($name); From 1caad2f1d1811d75da9f07045e2eb22fd17fda5c Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 07:48:55 +0100 Subject: [PATCH 04/12] Bug 1983391: Remove the unused Donation user-setting subclass Bugzilla::User::Setting::Donation defined a custom validate_value(), but no setting declared subclass => 'Donation' in Bugzilla::Install::SETTINGS, so the class was never loaded. It is dead code on two counts: the subclass is unwired, and Bugzilla::User::Setting::set() writes directly to the database without calling validate_value() at all. The values it guarded are only ever set from trusted sources -- BUGZILLA_VERSION for the version, and validate_date()-checked input for the reminder date -- so no validation is lost by removing it. --- Bugzilla/User/Setting/Donation.pm | 51 ------------------------------- 1 file changed, 51 deletions(-) delete mode 100644 Bugzilla/User/Setting/Donation.pm diff --git a/Bugzilla/User/Setting/Donation.pm b/Bugzilla/User/Setting/Donation.pm deleted file mode 100644 index e67a2f3661..0000000000 --- a/Bugzilla/User/Setting/Donation.pm +++ /dev/null @@ -1,51 +0,0 @@ -# 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. - -package Bugzilla::User::Setting::Donation; - -use 5.10.1; -use strict; -use warnings; - -use base qw(Bugzilla::User::Setting); - -use Bugzilla::Error; -use Bugzilla::Util qw(trick_taint validate_date); - -sub validate_value { - my ($self, $value) = @_; - my $name = $self->{'_setting_name'}; - - if ($name eq 'donate_banner_pref') { - return $self->SUPER::validate_value($value); - } - - if ($name eq 'donate_banner_last_version') { - if ($value =~ /^0$|^[0-9A-Za-z][0-9A-Za-z._+-]*$/) { - trick_taint($value); - return; - } - } - elsif ($name eq 'donate_banner_reminder_date') { - if ($value eq '1970-01-01' || validate_date($value)) { - trick_taint($value); - return; - } - } - - ThrowCodeError('setting_value_invalid', {name => $name, value => $value}); -} - -1; - -__END__ - -=head1 NAME - -Bugzilla::User::Setting::Donation - Donation banner user settings - -=cut From b8287646d9efc886bc03b32111b5b4ad0ca7414f Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 07:58:42 +0100 Subject: [PATCH 05/12] Bug 1983391: Do not let the donation banner hide the new-release notice index.html.tmpl rendered the donation banner and the new-release / end-of- support notice as mutually exclusive ([% IF donation %] ... [% ELSIF release %]), both sharing the #new_release element. Because the banner is shown to admins by default until dismissed, it suppressed the upgrade and end-of-support security notice from Bugzilla::Update::get_notifications(). Render the two as independent blocks so both can appear. The banner now uses its own .donation_banner container instead of reusing the #new_release id (which also avoids duplicate ids on the page); the CSS selectors are updated to the class and given the margin/padding they previously inherited from #new_release. --- skins/standard/global.css | 6 ++++-- template/en/default/index.html.tmpl | 6 ++++-- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/skins/standard/global.css b/skins/standard/global.css index 9080f58cc7..3127219b18 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -1050,7 +1050,9 @@ input[type="radio"]:checked { color: var(--secondary-label-color); } -#new_release.donation_banner { +.donation_banner { + margin: 1em; + padding: 0.5em 1em; border: 2px solid rgba(var(--accent-color-green-1), 0.75); color: var(--primary-text-color); background: var(--primary-region-background-color); @@ -1176,7 +1178,7 @@ input[type="radio"]:checked { } @media screen and (prefers-color-scheme: dark) { - #new_release.donation_banner { + .donation_banner { border-color: rgba(var(--accent-color-green-1), 0.6); background: rgba(40, 46, 43, 0.95); } diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl index 63f8a7ccc5..edb5ba71e7 100644 --- a/template/en/default/index.html.tmpl +++ b/template/en/default/index.html.tmpl @@ -33,7 +33,7 @@ %] [% IF donation %] -
+
-[% ELSIF release %] +[% END %] + +[% IF release %]
[% IF release.data %] [% IF release.eos_date %] From bba189b8e53fdf0e8acff2431b48d2d6f9db7efc Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 08:19:06 +0100 Subject: [PATCH 06/12] Bug 1983391: Share common banner styling between release and donation banners The 'do not hide the new-release notice' change split the donation banner off the #new_release element, which dropped the shared box styling (margins, padding, and the small-print .notice styling) from the donation banner. Introduce a .home_banner base class carrying the shared box styling and .notice sizing, applied to both the #new_release and .donation_banner elements. Each banner keeps only its distinctive bits (red vs green border color, bold vs normal weight, the donation layout/shadow). The shared rule uses the border-width/-style longhands so it never resets each banner's own border-color regardless of stylesheet load order. --- skins/standard/global.css | 5 +---- skins/standard/index.css | 17 +++++++++++++---- template/en/default/index.html.tmpl | 4 ++-- 3 files changed, 16 insertions(+), 10 deletions(-) diff --git a/skins/standard/global.css b/skins/standard/global.css index 3127219b18..c83fad1a68 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -1051,13 +1051,10 @@ input[type="radio"]:checked { } .donation_banner { - margin: 1em; - padding: 0.5em 1em; - border: 2px solid rgba(var(--accent-color-green-1), 0.75); + border-color: rgba(var(--accent-color-green-1), 0.75); color: var(--primary-text-color); background: var(--primary-region-background-color); box-shadow: var(--primary-region-box-shadow); - border-radius: 8px; font-weight: normal; } diff --git a/skins/standard/index.css b/skins/standard/index.css index 07e67c2885..2d8e54c907 100644 --- a/skins/standard/index.css +++ b/skins/standard/index.css @@ -21,18 +21,27 @@ text-align: center; } - #new_release { + /* Shared styling for home page banners (new release + donation). Each banner + sets its own border-color; the shared rule uses the border-width/-style + longhands so it never resets that color regardless of stylesheet order. */ + .home_banner { margin: 1em; - border: 2px solid rgb(var(--accent-color-red-1)); padding: 0.5em 1em; - font-weight: bold; + border-width: 2px; + border-style: solid; + border-radius: 8px; } - #new_release .notice { + .home_banner .notice { font-size: var(--font-size-small); font-weight: normal; } + #new_release { + border-color: rgb(var(--accent-color-red-1)); + font-weight: bold; + } + #welcome-admin a { font-weight: bold; } diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl index edb5ba71e7..c7f9d21abe 100644 --- a/template/en/default/index.html.tmpl +++ b/template/en/default/index.html.tmpl @@ -33,7 +33,7 @@ %] [% IF donation %] -
+
+
[% IF release.data %] [% IF release.eos_date %]

[% terms.Bugzilla %] [%+ release.branch_version FILTER html %] will From 67d1a7649ebdf0088976e059e4b4f9ed117d673f Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 08:19:47 +0100 Subject: [PATCH 07/12] Bug 1983391: Use a stateless hash token for the donation banner dismiss Bugzilla::Donation::get_banner() issued a session token on every home page render that showed the banner, writing a row to the tokens table each time and leaving it there until the (single-use) form was submitted or the row aged out. For a banner shown to every eligible user on every uncached home page view that is needless database churn. Switch to issue_hash_token()/check_hash_token(), which are stateless: the token is an HMAC of the user and a data tag, validated by recomputation with no database row and nothing to delete afterwards. Same CSRF protection, no churn. --- Bugzilla/Donation.pm | 4 ++-- index.cgi | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Bugzilla/Donation.pm b/Bugzilla/Donation.pm index c062b4a673..6e5d966acc 100644 --- a/Bugzilla/Donation.pm +++ b/Bugzilla/Donation.pm @@ -12,7 +12,7 @@ use strict; use warnings; use Bugzilla::Constants; -use Bugzilla::Token qw(issue_session_token); +use Bugzilla::Token qw(issue_hash_token); use DateTime; @@ -67,7 +67,7 @@ sub get_banner { show_thanks => $show_thanks, visibility => $visibility, settings_link => 'editparams.cgi?section=donation#donation_banner_visibility_desc', - token => issue_session_token('edit_user_prefs'), + token => issue_hash_token(['donation_banner']), }; if ($visibility eq 'admins_only') { diff --git a/index.cgi b/index.cgi index dee6e3dbd6..8007af226d 100755 --- a/index.cgi +++ b/index.cgi @@ -51,10 +51,9 @@ if ($donate_action) { Bugzilla->login(LOGIN_REQUIRED) unless Bugzilla->user->id; my $token = $cgi->param('token'); - check_token_data($token, 'edit_user_prefs'); + check_hash_token($token, ['donation_banner']); my $redirect = Bugzilla::Donation::set_banner_preference($donate_action); - delete_token($token); print $cgi->redirect(-uri => $redirect); exit; From 1d1942c85066b8aa0e1536d33f3dc791aa8a09f6 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 08:20:07 +0100 Subject: [PATCH 08/12] Bug 1983391: Stop stretching the Buggie donation image buggie.png is a tall portrait image (78x215) but .donation_banner_image forces it into a 110x110 square, distorting it horizontally. Add object-fit: contain (centered) so Buggie keeps his aspect ratio and sits centered within the frame. --- skins/standard/global.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/skins/standard/global.css b/skins/standard/global.css index c83fad1a68..7858369d42 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -1068,6 +1068,8 @@ input[type="radio"]:checked { width: 110px; height: 110px; flex: 0 0 auto; + object-fit: contain; + object-position: center; border: 1px dashed rgba(var(--accent-color-green-1), 0.7); border-radius: 10px; background: rgba(var(--accent-color-green-1), 0.08); From 02edf73dfda6842138f4668604a3cff27e4b180b Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 08:20:37 +0100 Subject: [PATCH 09/12] Bug 1983391: Only show the reminder date input for 'specific date' On the Donate preferences tab the date picker was always visible, even for the 'next upgrade' and 'never' choices where it does nothing, which is confusing. Hide the date row by default (unless the saved preference is specific_date) and toggle it from the reminder dropdown via a small CSP-nonced script. --- template/en/default/account/prefs/donate.html.tmpl | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/template/en/default/account/prefs/donate.html.tmpl b/template/en/default/account/prefs/donate.html.tmpl index f31a9529b4..084fc6c5d5 100644 --- a/template/en/default/account/prefs/donate.html.tmpl +++ b/template/en/default/account/prefs/donate.html.tmpl @@ -32,7 +32,7 @@ - + + + From 3a4c24367794efa69b1fd0baae2d968a653f22ca Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 08:21:21 +0100 Subject: [PATCH 10/12] Bug 1983391: Improve donation banner contrast in dark mode The dim rgba(green, 0.6) border barely separated the banner from the dark page background. Use the brighter --accent-color-lightgreen-1 for the border (and a low-alpha lightgreen tint for the background) so the banner stands out while the light body text stays readable. --- skins/standard/global.css | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/skins/standard/global.css b/skins/standard/global.css index 7858369d42..6ce3802ab2 100644 --- a/skins/standard/global.css +++ b/skins/standard/global.css @@ -1178,13 +1178,13 @@ input[type="radio"]:checked { @media screen and (prefers-color-scheme: dark) { .donation_banner { - border-color: rgba(var(--accent-color-green-1), 0.6); - background: rgba(40, 46, 43, 0.95); + border-color: rgb(var(--accent-color-lightgreen-1)); + background: rgba(var(--accent-color-lightgreen-1), 0.12); } .donation_banner_image { - border-color: rgba(var(--accent-color-green-1), 0.55); - background: rgba(var(--accent-color-green-1), 0.12); + border-color: rgba(var(--accent-color-lightgreen-1), 0.55); + background: rgba(var(--accent-color-lightgreen-1), 0.12); } .donation_banner_form_subtle { From 8d08751adc1c5f4f5225b0505acecabdc341978e Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 08:24:18 +0100 Subject: [PATCH 11/12] Bug 1983391: Constrain home banners to the page content width The banners rendered full width (~1250px at a 1280px viewport) while the welcome heading and tiles are a centered 640px column, so the banner looked oddly wide and disconnected. Move both banners inside #page-index and cap .home_banner at the content width (max-width: 640px, border-box) centered with auto side margins, so they line up with the tiles and gain proper gutters. --- skins/standard/index.css | 4 +++- template/en/default/index.html.tmpl | 3 ++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/skins/standard/index.css b/skins/standard/index.css index 2d8e54c907..926201b81b 100644 --- a/skins/standard/index.css +++ b/skins/standard/index.css @@ -25,7 +25,9 @@ sets its own border-color; the shared rule uses the border-width/-style longhands so it never resets that color regardless of stylesheet order. */ .home_banner { - margin: 1em; + box-sizing: border-box; + max-width: 640px; + margin: 1em auto; padding: 0.5em 1em; border-width: 2px; border-style: solid; diff --git a/template/en/default/index.html.tmpl b/template/en/default/index.html.tmpl index c7f9d21abe..4e85362260 100644 --- a/template/en/default/index.html.tmpl +++ b/template/en/default/index.html.tmpl @@ -32,6 +32,8 @@ no_yui = 1 %] +

+ [% IF donation %]
@@ -126,7 +128,6 @@
[% END %] -
From a33d3854a371b6580f27ef3cf48061476dceccb7 Mon Sep 17 00:00:00 2001 From: Martin Renvoize Date: Fri, 3 Jul 2026 09:14:02 +0100 Subject: [PATCH 12/12] Bug 1983391: Default the reminder date picker to today, not the epoch donate_banner_reminder_date defaults to 1970-01-01 as a 'show immediately' sentinel, which the Donate tab date picker rendered literally, forcing the user to navigate forward decades. Since a reminder is always a future date, fall back to today (computed in Bugzilla->local_timezone, matching get_banner and set_banner_preference) when the stored value is still the epoch sentinel. --- template/en/default/account/prefs/donate.html.tmpl | 5 +++++ userprefs.cgi | 4 ++++ 2 files changed, 9 insertions(+) diff --git a/template/en/default/account/prefs/donate.html.tmpl b/template/en/default/account/prefs/donate.html.tmpl index 084fc6c5d5..b0f4416915 100644 --- a/template/en/default/account/prefs/donate.html.tmpl +++ b/template/en/default/account/prefs/donate.html.tmpl @@ -9,6 +9,11 @@ [% donate_pref = user.settings.donate_banner_pref.value; reminder_date = user.settings.donate_banner_reminder_date.value; + # The setting defaults to the epoch as a "show immediately" sentinel; show + # today in the picker instead, since reminders are always future dates. + IF !reminder_date || reminder_date == '1970-01-01'; + reminder_date = today; + END; %]

diff --git a/userprefs.cgi b/userprefs.cgi index efc00d00ba..561ae3826e 100755 --- a/userprefs.cgi +++ b/userprefs.cgi @@ -259,6 +259,10 @@ sub DoDonate { my $user = Bugzilla->user; $vars->{settings} = $user->settings; + + # Default the reminder date picker to today rather than the stored epoch + # sentinel, since a reminder is always meant to be a future date. + $vars->{today} = DateTime->now(time_zone => Bugzilla->local_timezone)->ymd; } sub SaveDonate {