Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
22 commits
Select commit Hold shift + click to select a range
fc18a7a
Bug 1983391: Add a donation banner to the home page
justdave Jul 1, 2026
54d8115
Bug 1983391: Dismiss the next-upgrade donation reminder from the Dona…
mrenvoize Jul 3, 2026
aeaa19f
Bug 1983391: Hide internal donation settings from the generic Setting…
mrenvoize Jul 3, 2026
f894358
Bug 1983391: Remove the unused Donation user-setting subclass
mrenvoize Jul 3, 2026
baa6ddd
Bug 1983391: Do not let the donation banner hide the new-release notice
mrenvoize Jul 3, 2026
02e4776
Bug 1983391: Share common banner styling between release and donation…
mrenvoize Jul 3, 2026
2764c49
Bug 1983391: Use a stateless hash token for the donation banner dismiss
mrenvoize Jul 3, 2026
54871ab
Bug 1983391: Stop stretching the Buggie donation image
mrenvoize Jul 3, 2026
0fae8fb
Bug 1983391: Only show the reminder date input for 'specific date'
mrenvoize Jul 3, 2026
52d9396
Bug 1983391: Improve donation banner contrast in dark mode
mrenvoize Jul 3, 2026
b05b929
Bug 1983391: Constrain home banners to the page content width
mrenvoize Jul 3, 2026
c9ede8f
Bug 1983391: Default the reminder date picker to today, not the epoch
mrenvoize Jul 3, 2026
6ff7080
Bug 1983391: Replace donation banner placeholder image with buggie_wa…
mrenvoize Aug 3, 2026
01bc611
Convert the entire thing to an extension.
justdave Aug 26, 2026
a25b3ab
Put the dismissal UI in a disclosure widget for less noise
justdave Aug 26, 2026
1568255
Show the banner on the user preference page as well.
justdave Aug 26, 2026
e395f64
Use the good SVG file instead of the PNG mockup.
justdave Aug 26, 2026
e0219da
Let users know how to change their mind later
justdave Aug 26, 2026
b3d9acc
brighter background behind the image
justdave Aug 26, 2026
56c7e23
only load css and js when the banner is displayed
justdave Nov 1, 2026
775d2e1
Don't use a ThrowCodeError that only exists in the BMO extension
justdave Nov 1, 2026
dc27446
Add tests. Designed by Claude Sonnet and implemented by GPT-6 Luna.
justdave Nov 1, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions extensions/BugzillaDonationBanner/Config.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# 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::Extension::BugzillaDonationBanner;

use 5.14.0;
use strict;
use warnings;

use constant NAME => 'BugzillaDonationBanner';

__PACKAGE__->NAME;
124 changes: 124 additions & 0 deletions extensions/BugzillaDonationBanner/Extension.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
# 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::Extension::BugzillaDonationBanner;

use 5.14.0;
use strict;
use warnings;
use base qw(Bugzilla::Extension);

use Bugzilla::Constants;
use Bugzilla::Install::Filesystem;
use Bugzilla::User::Setting qw(add_setting);
use Bugzilla::Extension::BugzillaDonationBanner::Donation;

our $VERSION = '1.0';

sub config_add_panels {
my ($self, $args) = @_;
$args->{panel_modules}->{Donation}
= 'Bugzilla::Extension::BugzillaDonationBanner::Config';
}

sub install_before_final_checks {
add_setting({
name => 'donate_banner_pref',
options => ['next_upgrade', 'specific_date', 'never'],
default => 'next_upgrade',
category => 'Bugzilla.org',
});
add_setting({
name => 'donate_banner_last_version',
default => '0.0',
category => 'Bugzilla.org',
});
add_setting({
name => 'donate_banner_reminder_date',
default => '1970-01-01',
category => 'Bugzilla.org',
});
}

sub install_filesystem {
my ($self, $args) = @_;
my $files = $args->{files};
my $extension_dir = bz_locations()->{extensionsdir} . '/'
. __PACKAGE__->NAME;

$files->{"$extension_dir/bin/donate.cgi"}
= {perms => Bugzilla::Install::Filesystem::WS_EXECUTE};
$files->{"$extension_dir/web/buggie_watering.svg"}
= {perms => Bugzilla::Install::Filesystem::WS_SERVE};
$files->{"$extension_dir/web/donation-banner.css"}
= {perms => Bugzilla::Install::Filesystem::WS_SERVE};
$files->{"$extension_dir/web/donation-banner.js"}
= {perms => Bugzilla::Install::Filesystem::WS_SERVE};
}

sub app_startup {
my ($self, $args) = @_;
my $app = $args->{app};
my $r = $app->routes;

Bugzilla::App::CGI->load_one(
'bugzilla_donation_banner_cgi',
'extensions/BugzillaDonationBanner/bin/donate.cgi'
);
$r->post('/extensions/BugzillaDonationBanner/bin/donate.cgi')
->to('CGI#bugzilla_donation_banner_cgi');
}

sub template_before_process {
my ($self, $args) = @_;
my ($file, $vars) = @$args{qw(file vars)};

if ($file =~ m{(?:^|/)index\.html\.tmpl$}) {
$vars->{donation}
= Bugzilla::Extension::BugzillaDonationBanner::Donation::get_banner();
}
}

sub user_preferences_settings {
my ($self, $args) = @_;
my $skip_settings = $args->{skip_settings};
$skip_settings->{donate_banner_pref} = 1;
$skip_settings->{donate_banner_last_version} = 1;
$skip_settings->{donate_banner_reminder_date} = 1;
}

sub user_preferences {
my ($self, $args) = @_;
return unless $args->{current_tab} eq 'donate';

my $vars = $args->{vars};
my $user = Bugzilla->user;
my $cgi = Bugzilla->cgi;

if ($args->{save_changes}) {
my $pref = $cgi->param('donate_banner_pref') || 'next_upgrade';
my $date = $cgi->param('donate_banner_reminder_date') || '';
Bugzilla::Extension::BugzillaDonationBanner::Donation::save_preference(
$pref, $date);
$vars->{changes_saved} = 1;
}

$vars->{settings} = $user->settings(1);
$vars->{donation}
= Bugzilla::Extension::BugzillaDonationBanner::Donation::get_banner(1);
$vars->{donate_pref} = $user->setting('donate_banner_pref');
$vars->{today}
= Bugzilla::Extension::BugzillaDonationBanner::Donation::today();
my $reminder_date = $user->setting('donate_banner_reminder_date');
$vars->{reminder_date}
= !$reminder_date || $reminder_date eq '1970-01-01'
? $vars->{today}
: $reminder_date;
${$args->{handled}} = 1;
}

__PACKAGE__->NAME;
29 changes: 29 additions & 0 deletions extensions/BugzillaDonationBanner/bin/donate.cgi
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
#!/usr/bin/env perl

use 5.14.0;
use strict;
use warnings;

use lib qw(../../.. ../../../lib);

use Bugzilla;
use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Token qw(check_hash_token);
use Bugzilla::Extension::BugzillaDonationBanner::Donation;

my $cgi = Bugzilla->cgi;
if ($cgi->request_method ne 'POST') {
print $cgi->header(-status => '405 Method Not Allowed', -allow => 'POST');
exit;
}

Bugzilla->login(LOGIN_REQUIRED) unless Bugzilla->user->id;
check_hash_token($cgi->param('token'), ['donation_banner']);

Bugzilla::Extension::BugzillaDonationBanner::Donation::dismiss(
scalar $cgi->param('donate_action'),
scalar $cgi->param('donate_banner_reminder_date'),
);

$cgi->base_redirect();
28 changes: 28 additions & 0 deletions extensions/BugzillaDonationBanner/lib/Config.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
# 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::Extension::BugzillaDonationBanner::Config;

use 5.14.0;
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;
130 changes: 130 additions & 0 deletions extensions/BugzillaDonationBanner/lib/Donation.pm
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
# 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::Extension::BugzillaDonationBanner::Donation;

use 5.14.0;
use strict;
use warnings;

use Bugzilla::Constants;
use Bugzilla::Error;
use Bugzilla::Token qw(issue_hash_token);
use Bugzilla::User::Setting qw(clear_settings_cache);
use Bugzilla::Util qw(validate_date);

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 today {
return DateTime->now(time_zone => Bugzilla->local_timezone)->ymd;
}

sub get_banner {
my ($ignore_preference) = @_;
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 $reminder_date
= $settings->{donate_banner_reminder_date}->{value} || '1970-01-01';
my $current_version = BUGZILLA_VERSION;

my ($show, $show_thanks) = (0, 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') {
$show = $reminder_date le today();
}

return undef if !$show && !$ignore_preference;

my @messages = BANNER_MESSAGES;
my $data = {
url => BANNER_URL,
message => $messages[int(rand(@messages))],
show_thanks => $show_thanks,
token => issue_hash_token(['donation_banner']),
today => today(),
settings_link => 'editparams.cgi?section=donation#donation_banner_visibility_desc',
};

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 save_preference {
my ($pref, $date) = @_;
my $user = Bugzilla->user;
my $settings = $user->settings;

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') {
ThrowUserError('invalid_parameter', {
name => 'donate_banner_pref',
err => "must be one of 'next_upgrade', 'specific_date', or 'never'",
});
}

$settings->{donate_banner_pref}->set($pref);
$settings->{donate_banner_last_version}->set(BUGZILLA_VERSION);
clear_settings_cache($user->id);
}

sub dismiss {
my ($action, $date) = @_;
my $settings = Bugzilla->user->settings;

if ($action eq 'next_upgrade') {
save_preference('next_upgrade', '');
}
elsif ($action eq 'week' || $action eq 'month') {
my $dt = DateTime->now(time_zone => Bugzilla->local_timezone);
$dt->add(days => $action eq 'week' ? 7 : 30);
save_preference('specific_date', $dt->ymd);
}
elsif ($action eq 'date') {
save_preference('specific_date', $date);
}
elsif ($action eq 'never') {
save_preference('never', '');
}
}

1;
Loading
Loading