summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extensions/BMO/Extension.pm99
-rw-r--r--extensions/BMO/t/bounty_attachment.t60
-rw-r--r--extensions/BMO/template/en/default/hook/bug/show-header-end.html.tmpl11
-rw-r--r--extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl4
-rw-r--r--extensions/BMO/template/en/default/pages/attachment_bounty_form.html.tmpl194
-rw-r--r--extensions/BMO/web/js/attachment_bounty_form.js18
6 files changed, 386 insertions, 0 deletions
diff --git a/extensions/BMO/Extension.pm b/extensions/BMO/Extension.pm
index d679ac91e..1e0593c4e 100644
--- a/extensions/BMO/Extension.pm
+++ b/extensions/BMO/Extension.pm
@@ -44,6 +44,7 @@ use Encode qw(find_encoding encode_utf8);
use File::MimeInfo::Magic;
use List::MoreUtils qw(natatime);
use Scalar::Util qw(blessed);
+use List::Util qw(first);
use Sys::Syslog qw(:DEFAULT setlogsock);
use Bugzilla::Extension::BMO::Constants;
@@ -189,6 +190,104 @@ sub page_before_template {
elsif ($page eq 'query_database.html') {
query_database($vars);
}
+ elsif ($page eq 'attachment_bounty_form.html') {
+ bounty_attachment($vars);
+ }
+}
+
+sub bounty_attachment {
+ my ($vars) = @_;
+ ThrowUserError('user_not_insider') unless Bugzilla->user->is_insider;
+
+ my $input = Bugzilla->input_params;
+ my $dbh = Bugzilla->dbh;
+ my $bug = Bugzilla::Bug->check({ id => $input->{bug_id}, cache => 1 });
+ my $attachment = first { $_ && is_bounty_attachment($_) } @{$bug->attachments};
+ $vars->{bug} = $bug;
+
+ if ($input->{submit}) {
+ ThrowUserError('bounty_attachment_missing_reporter')
+ unless $input->{reporter_email};
+
+ my @fields = qw( reporter_email amount_paid reported_date fixed_date awarded_date publish );
+ my %form = map { $_ => $input->{$_} } @fields;
+ $form{credit} = [ grep { defined } map { $input->{"credit_$_"} } 1..3 ];
+
+ $dbh->bz_start_transaction();
+ if ($attachment) {
+ $attachment->set(
+ description => format_bounty_attachment_description(\%form)
+ );
+ $attachment->update;
+ }
+ else {
+ my $attachment = Bugzilla::Attachment->create({
+ bug => $bug,
+ isprivate => 1,
+ mimetype => 'text/plain',
+ data => 'bounty',
+ filename => 'bugbounty.data',
+ description => format_bounty_attachment_description(\%form),
+ });
+ }
+ $dbh->bz_commit_transaction();
+
+ print Bugzilla->cgi->redirect('show_bug.cgi?id=' . $bug->id);
+ exit;
+ }
+
+ if ($attachment) {
+ my $form = parse_bounty_attachment_description($attachment->description);
+ $vars->{form} = $form;
+ }
+}
+
+sub is_bounty_attachment {
+ my ($attachment) = @_;
+
+ return 0 unless $attachment->filename eq 'bugbounty.data';
+ return 0 unless $attachment->contenttype eq 'text/plain';
+ return 0 unless $attachment->isprivate;
+ return $attachment->description =~ /^(?:[^,]*,)+[^,]*$/;
+}
+
+sub format_bounty_attachment_description {
+ my ($form) = @_;
+ my @fields = (
+ @$form{qw( reporter_email amount_paid reported_date fixed_date awarded_date )},
+ $form->{publish} ? 'true' : 'false',
+ @{ $form->{credit} // [] }
+ );
+
+ return join(',', map { $_ // '' } @fields);
+}
+
+sub parse_bounty_attachment_description {
+ my ($desc) = @_;
+
+ my %map = ( true => 1, false => 0 );
+ my $date = qr/\d{4}-\d{2}-\d{2}/;
+ $desc =~ m!
+ ^
+ (?<reporter_email> [^,]+) \s*,\s*
+ (?<amount_paid> [0-9]+) ? \s*,\s*
+ (?<reported_date> $date) ? \s*,\s*
+ (?<fixed_date> $date) ? \s*,\s*
+ (?<awarded_date> $date) ? \s*,\s*
+ (?<publish> (?i: true | false )) ?
+ (?: \s*,\s* (?<credits>.*) ) ?
+ $
+ !x;
+
+ return {
+ reporter_email => $+{reporter_email} // '',
+ amount_paid => $+{amount_paid} // '',
+ reported_date => $+{reported_date} // '',
+ fixed_date => $+{fixed_date} // '',
+ awarded_date => $+{awarded_date} // '',
+ publish => $map{ $+{publish} // 'false' },
+ credit => [grep { $_ } split(/\s*,\s*/, $+{credits}) ]
+ };
}
sub _get_field_values_sort_key {
diff --git a/extensions/BMO/t/bounty_attachment.t b/extensions/BMO/t/bounty_attachment.t
new file mode 100644
index 000000000..bd79b0dfe
--- /dev/null
+++ b/extensions/BMO/t/bounty_attachment.t
@@ -0,0 +1,60 @@
+#!/usr/bin/perl -T
+# 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.
+use strict;
+use warnings;
+use lib qw( . lib );
+
+use Test::More;
+use Bugzilla;
+use Bugzilla::Extension;
+
+my $class = Bugzilla::Extension->load('extensions/BMO/Extension.pm',
+ 'extensions/BMO/Config.pm');
+
+my $parse = $class->can('parse_bounty_attachment_description');
+my $format = $class->can('format_bounty_attachment_description');
+
+ok($parse, "got the function");
+
+my $bughunter = $parse->('bughunter@hacker.org, , 2014-06-25, , ,false');
+is_deeply({ reporter_email => 'bughunter@hacker.org',
+ amount_paid => '',
+ reported_date => '2014-06-25',
+ fixed_date => '',
+ awarded_date => '',
+ publish => 0,
+ credit => []}, $bughunter);
+
+my $hfli = $parse->('hfli@fortinet.com, 1000, 2010-07-16, 2010-08-04, 2011-06-15, true, Fortiguard Labs');
+is_deeply({ reporter_email => 'hfli@fortinet.com',
+ amount_paid => '1000',
+ reported_date => '2010-07-16',
+ fixed_date => '2010-08-04',
+ awarded_date => '2011-06-15',
+ publish => 1,
+ credit => ['Fortiguard Labs']}, $hfli);
+
+is('batman@justiceleague.america,1000,2015-01-01,2015-02-02,2015-03-03,true,JLA,Wayne Industries,Test',
+ $format->({ reporter_email => 'batman@justiceleague.america',
+ amount_paid => 1000,
+ reported_date => '2015-01-01',
+ fixed_date => '2015-02-02',
+ awarded_date => '2015-03-03',
+ publish => 1,
+ credit => ['JLA', 'Wayne Industries', 'Test'] }));
+
+my $dylan = $parse->('dylan@hardison.net,2,2014-09-23,2014-09-24,2014-09-25,true,Foo bar,Bork,');
+is_deeply({ reporter_email => 'dylan@hardison.net',
+ amount_paid => 2,
+ reported_date => '2014-09-23',
+ fixed_date => '2014-09-24',
+ awarded_date => '2014-09-25',
+ publish => 1,
+ credit => ['Foo bar', 'Bork']}, $dylan);
+
+done_testing;
diff --git a/extensions/BMO/template/en/default/hook/bug/show-header-end.html.tmpl b/extensions/BMO/template/en/default/hook/bug/show-header-end.html.tmpl
index 5ab189045..ff2438ffd 100644
--- a/extensions/BMO/template/en/default/hook/bug/show-header-end.html.tmpl
+++ b/extensions/BMO/template/en/default/hook/bug/show-header-end.html.tmpl
@@ -8,6 +8,10 @@
[% style_urls.push('extensions/BMO/web/styles/edit_bug.css') %]
[% javascript_urls.push('extensions/BMO/web/js/edit_bug.js') %]
+[% IF user.is_insider %]
+ [% javascript_urls.push('extensions/BMO/web/js/attachment_bounty_form.js') %]
+ [% yui.push('selector') %]
+[% END %]
[% title = "$bug.bug_id &ndash; " %]
[% IF bug.alias != '' %]
[% title = title _ "($bug.alias) " %]
@@ -16,3 +20,10 @@
[% javascript = javascript _
"document.title = document.title.replace(/^" _ terms.Bug _ " /, '');"
%]
+[% js_bug_id = bug.bug_id FILTER js %]
+[% IF user.is_insider %]
+ [% javascript = javascript _
+ "YAHOO.util.Event.onDOMReady(function () { " _
+ "add_bounty_attachment('$js_bug_id'); });"
+ %]
+[% END %]
diff --git a/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
index aaf23fff5..9caf9bd29 100644
--- a/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
+++ b/extensions/BMO/template/en/default/hook/global/user-error-errors.html.tmpl
@@ -37,4 +37,8 @@
[% title = "Too Many Bugs" %]
Too many [% terms.bugs %] matched your selection criteria.
+[% ELSIF error == "bounty_attachment_missing_reporter" %]
+ [% title = "Missing Reporter" %]
+ You must provide an email address for a bounty attachment.
+
[% END %]
diff --git a/extensions/BMO/template/en/default/pages/attachment_bounty_form.html.tmpl b/extensions/BMO/template/en/default/pages/attachment_bounty_form.html.tmpl
new file mode 100644
index 000000000..31a73b017
--- /dev/null
+++ b/extensions/BMO/template/en/default/pages/attachment_bounty_form.html.tmpl
@@ -0,0 +1,194 @@
+[%# 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.
+ #%]
+[% PROCESS global/variables.none.tmpl %]
+
+[% inline_style = BLOCK %]
+#bounty_form {
+ padding: 10px;
+}
+#bounty_form .required:after {
+ content: " *";
+ color: red;
+}
+#bounty_form .field_label {
+ font-weight: bold;
+ display: block;
+ text-align: left;
+}
+#bounty_form .field_desc {
+ padding-bottom: 3px;
+}
+#bounty_form .field_desc,
+#bounty_form .head_desc {
+ width: 600px;
+ word-wrap: normal;
+}
+#bounty_form .head_desc {
+ padding-top: 5px;
+ padding-bottom: 12px;
+}
+#bounty_form .form_section {
+ margin-bottom: 10px;
+}
+#bounty_form textarea {
+ font-family: inherit;
+ font-size: inherit;
+}
+#bounty_form em {
+ font-size: 1em;
+}
+.yui-calcontainer {
+ z-index: 2;
+}
+[% END %]
+
+[% inline_javascript = BLOCK %]
+function validateAndSubmit() {
+ 'use strict';
+ var alert_text = '';
+ var requiredLabels = YAHOO.util.Selector.query('label.required');
+ if (requiredLabels) {
+ requiredLabels.forEach(function (label) {
+ var name = label.getAttribute('for');
+ var ids = YAHOO.util.Selector.query(
+ '#bounty_form *[name="' + name + '"]'
+ ).map(function (e) {
+ return e.id
+ });
+
+ if (ids && ids[0]) {
+ if (!isFilledOut(ids[0])) {
+ var desc = label.textContent || name;
+ alert_text +=
+ "Please enter a value for " +
+ desc.replace(/[\r\n]+/, "").replace(/\s+/g, " ") +
+ "\n";
+ }
+ }
+ });
+ }
+
+ if (alert_text != '') {
+ alert(alert_text);
+ return false;
+ }
+ return true;
+}
+[% END %]
+
+[% PROCESS global/header.html.tmpl
+ title = "Bounty Attachment Form"
+ style = inline_style
+ javascript = inline_javascript
+ javascript_urls = [ 'extensions/BMO/web/js/form_validate.js',
+ 'js/field.js', 'js/util.js' ]
+ yui = [ "calendar", "selector" ]
+%]
+
+[% USE Bugzilla %]
+[% cgi = Bugzilla.cgi %]
+
+<form id="bounty_form" method="post" action="page.cgi"
+ enctype="multipart/form-data" onSubmit="return validateAndSubmit();">
+
+ <input type="hidden" name="bug_id" value="[% bug.id FILTER none %]">
+ <input type="hidden" name="id" value="attachment_bounty_form.html">
+ <input type="hidden" name="submit" value="1">
+
+ <div class="head_desc">
+ Bounty Attachment for [% "$terms.Bug $bug.id" FILTER bug_link(bug.id) FILTER none %]
+ </div>
+
+ <div class="form_section">
+ <label for="reporter_email" class="field_label required">Reporter's Email</label>
+ <input type="text" name="reporter_email" id="reporter_email" size="80"
+ value="[% form.reporter_email FILTER none %]">
+ </div>
+
+ <div class="form_section">
+ <label for="amount_paid" class="field_label">Amount Paid</label>
+ <input type="text" name="amount_paid" id="amount_paid" size="80" value="[% form.amount_paid FILTER none %]">
+ </div>
+
+ <div class="form_section">
+ <label for="reported_date" class="field_label">Reported Date</label>
+ <input name="reported_date" size="20" id="reported_date" value="[% form.reported_date FILTER none %]"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_reported_date"
+ onclick="showCalendar('reported_date')">
+ <span>Calendar</span>
+ </button>
+ <div id="con_calendar_reported_date"></div>
+ <script type="text/javascript">
+ createCalendar('reported_date')
+ </script>
+ </div>
+
+ <div class="form_section">
+ <label for="fixed_date" class="field_label">Fixed Date</label>
+ <input name="fixed_date" size="20" id="fixed_date" value="[% form.fixed_date FILTER none %]"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_fixed_date"
+ onclick="showCalendar('fixed_date')">
+ <span>Calendar</span>
+ </button>
+ <div id="con_calendar_fixed_date"></div>
+ <script type="text/javascript">
+ createCalendar('fixed_date')
+ </script>
+ </div>
+
+ <div class="form_section">
+ <label for="awarded_date" class="field_label">Awarded Date</label>
+ <input name="awarded_date" size="20" id="awarded_date" value="[% form.awarded_date FILTER none %]"
+ onchange="updateCalendarFromField(this)">
+ <button type="button" class="calendar_button"
+ id="button_calendar_awarded_date"
+ onclick="showCalendar('awarded_date')">
+ <span>Calendar</span>
+ </button>
+ <div id="con_calendar_awarded_date"></div>
+ <script type="text/javascript">
+ createCalendar('awarded_date')
+ </script>
+ </div>
+
+ <div class="form_section">
+ <label for="publish" class="field_label">Publish</label>
+ <select name="publish" id="publish">
+ <option value="1"[% IF form.publish %]selected[% END %]>Yes</option>
+ <option value="0"[% UNLESS form.publish %]selected[% END %]>No</option>
+ </select>
+ </div>
+
+ <div class="form_section">
+ <label for="credit_1" class="field_label">Credit</label>
+ <input type="text" name="credit_1" id="credit_1" size="80" value="[% form.credit.0 FILTER none %]">
+ </div>
+
+ <div class="form_section">
+ <label for="credit_2" class="field_label">Credit</label>
+ <input type="text" name="credit_2" id="credit_2" size="80" value="[% form.credit.1 FILTER none %]">
+ </div>
+
+ <div class="form_section">
+ <label for="credit_3" class="field_label">Credit</label>
+ <input type="text" name="credit_3" id="credit_3" size="80" value="[% form.credit.2 FILTER none %]">
+ </div>
+
+ <input type="submit" id="commit" value="Submit">
+
+ <p>
+ [ <span class="required_star">*</span> <span class="required_explanation">
+ Required Field</span> ]
+ </p>
+</form>
+
+[% PROCESS global/footer.html.tmpl %]
diff --git a/extensions/BMO/web/js/attachment_bounty_form.js b/extensions/BMO/web/js/attachment_bounty_form.js
new file mode 100644
index 000000000..cdec78276
--- /dev/null
+++ b/extensions/BMO/web/js/attachment_bounty_form.js
@@ -0,0 +1,18 @@
+/* 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. */
+
+function add_bounty_attachment(bug_id) {
+ var nodes = YAHOO.util.Selector.query('#attachment_table tr.bz_attach_footer td');
+ if (nodes) {
+ var td = nodes[0];
+ var a = document.createElement('a');
+ a.href = 'page.cgi?id=attachment_bounty_form.html&bug_id=' + bug_id;
+ a.appendChild(document.createTextNode('Add bounty tracking attachment'));
+ td.appendChild(document.createElement('br'));
+ td.appendChild(a);
+ }
+}