├── .gitignore ├── README.md ├── contrib ├── bz_anonymize.sql ├── contrib_reorg-tools_movebugs.pl ├── contrib_reorg-tools_movecomponent.pl ├── dbbackup.sh ├── delete_bug.pl └── rename-user.pl ├── extensions ├── BFBSD │ ├── Config.pm │ ├── Extension.pm │ ├── lib │ │ ├── Auth │ │ │ └── Verify.pm │ │ └── Helpers.pm │ └── template │ │ └── en │ │ └── default │ │ └── hook │ │ └── bug │ │ └── edit-after_custom_fields.html.tmpl ├── Dashboard │ ├── Config.pm │ ├── Extension.pm │ ├── lib │ │ └── Util.pm │ ├── template │ │ └── en │ │ │ └── default │ │ │ ├── hook │ │ │ └── global │ │ │ │ ├── common-links-fbsd-links.html.tmpl │ │ │ │ └── user-error-errors.html.tmpl │ │ │ └── pages │ │ │ └── dashboard.html.tmpl │ └── web │ │ └── style.css ├── FBSDAutoAssign │ ├── Config.pm │ └── Extension.pm ├── FreeBSDBugUrls │ ├── Config.pm │ ├── Extension.pm │ ├── lib │ │ ├── BitBucket.pm │ │ ├── DragonFlyBSD.pm │ │ ├── FreshPorts.pm │ │ ├── Gitlabs.pm │ │ ├── GoogleChromium.pm │ │ ├── GoogleIssueTracker.pm │ │ ├── NetBSD.pm │ │ ├── Phabricator.pm │ │ └── Sourceforge.pm │ └── template │ │ └── en │ │ └── default │ │ └── hook │ │ └── global │ │ └── user-error-bug_url_invalid_tracker.html.tmpl ├── HideComponents │ ├── Config.pm │ └── Extension.pm ├── HideSPAM │ ├── Config.pm │ └── Extension.pm ├── InlineHistory │ ├── Config.pm │ ├── Extension.pm │ ├── README │ ├── template │ │ └── en │ │ │ └── default │ │ │ └── hook │ │ │ ├── bug │ │ │ ├── comments-aftercomments.html.tmpl │ │ │ ├── comments-comment_banner.html.tmpl │ │ │ └── show-header-end.html.tmpl │ │ │ └── global │ │ │ └── setting-descs-settings.none.tmpl │ └── web │ │ ├── inline-history.js │ │ └── style.css ├── Reporting │ ├── Config.pm │ ├── Extension.pm │ ├── lib │ │ ├── Charting.pm │ │ ├── Graphs.pm │ │ └── Queries.pm │ ├── template │ │ └── en │ │ │ └── default │ │ │ ├── hook │ │ │ └── global │ │ │ │ └── user-error-errors.html.tmpl │ │ │ └── pages │ │ │ ├── reporting.html.tmpl │ │ │ └── showreport.html.tmpl │ └── web │ │ └── style.css ├── SVNLinks │ ├── Config.pm │ └── Extension.pm └── SpamDelete │ ├── Config.pm │ ├── Extension.pm │ ├── lib │ └── Config.pm │ ├── template │ └── en │ │ └── default │ │ ├── admin │ │ └── params │ │ │ └── spamdelete.html.tmpl │ │ ├── hook │ │ ├── bug │ │ │ └── edit-after_custom_fields.html.tmpl │ │ └── global │ │ │ └── common-links-fbsd-links.html.tmpl │ │ └── pages │ │ ├── deletespam.html.tmpl │ │ ├── listsuspects.html.tmpl │ │ └── searchspam.html.tmpl │ └── web │ └── style.css ├── images ├── committer.png ├── fbsd_favicon.ico ├── googlesoc.png └── triager.png ├── monthlyexprun.pl ├── news.html.tmpl ├── skins └── contrib │ └── FreeBSD │ ├── global.css │ ├── index.css │ └── index │ ├── file-a-bug.png │ ├── help.png │ ├── new-account.png │ └── search.png ├── sync_freebsd_committers.pl ├── template └── en │ └── custom │ ├── attachment │ └── edit.html.tmpl │ ├── bug │ └── create │ │ └── create.html.tmpl │ ├── global │ ├── choose-product.html.tmpl │ ├── common-links.html.tmpl │ ├── header.html.tmpl │ ├── textarea.html.tmpl │ └── variables.none.tmpl │ ├── hook │ └── global │ │ └── user-error-errors.html.tmpl │ ├── index.html.tmpl │ ├── list │ └── table.html.tmpl │ ├── news.html.tmpl │ └── pages │ └── fields.html.tmpl └── weeklyreminder.pl /.gitignore: -------------------------------------------------------------------------------- 1 | *~ 2 | *# 3 | 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is freebsd/bugzilla? 2 | 3 | freebsd/bugzilla is a set of changes and extensions for Bugzilla being used 4 | by the FreeBSD project. Those usually map a Bugzilla source package layout 5 | in a 1:1 fashion. 6 | 7 | # How do I use it? 8 | 9 | freebsd/bugzilla will refer to a certain bugzilla package from the FreeBSD 10 | ports, usually the latest stable one (devel/bugzilla50 as of now). 11 | 12 | Thus, if you install devel/bugzilla50, you can simply copy 13 | freebsd/bugzilla over ${PREFIX}/www/bugzilla to have a Bugzilla implementation 14 | that resembles what the FreeBSD project uses at 15 | https://bugs.freebsd.org/bugzilla. 16 | 17 | In short: 18 | 19 | * cd /usr/ports/devel/bugzilla50 && make install clean 20 | * Download the latest stable freebsd/bugzilla release from https://github.com/freebsd/bugzilla 21 | * tar -C /usr/local/www/bugzilla --strip-components=1 -xjvf (filename).tar.gz 22 | * cd /usr/local/www/bugzilla && checksetup.pl 23 | -------------------------------------------------------------------------------- /contrib/bz_anonymize.sql: -------------------------------------------------------------------------------- 1 | SET SCHEMA 'invalid'; 2 | 3 | DO $$ 4 | DECLARE 5 | reassign integer; 6 | fbsdids integer[]; 7 | BEGIN 8 | SELECT userid INTO reassign 9 | FROM profiles 10 | WHERE login_name = 'nobody@FreeBSD.org'; 11 | SELECT array_agg(userid) INTO fbsdids 12 | FROM ( 13 | SELECT userid 14 | FROM profiles 15 | WHERE LOWER(login_name) LIKE '%@freebsd.org' 16 | ) as tmp; 17 | 18 | 19 | UPDATE attachments SET submitter_id = reassign WHERE NOT (submitter_id = ANY(fbsdids)); 20 | DELETE FROM audit_log; 21 | UPDATE bugs SET reporter = reassign WHERE NOT (reporter = ANY(fbsdids)); 22 | UPDATE bugs set assigned_to = reassign WHERE NOT (assigned_to = ANY(fbsdids)); 23 | UPDATE bugs_activity SET who = reassign WHERE NOT (who = ANY(fbsdids)); 24 | DELETE FROM cc; 25 | DELETE FROM component_cc; 26 | UPDATE components SET initialowner = reassign WHERE NOT (initialowner = ANY(fbsdids)); 27 | DELETE FROM email_setting; 28 | UPDATE flags set setter_id = reassign WHERE NOT (setter_id = ANY(fbsdids)); 29 | UPDATE flags set requestee_id = reassign WHERE NOT (requestee_id = ANY(fbsdids)); 30 | DELETE FROM login_failure; 31 | DELETE FROM logincookies; 32 | UPDATE longdescs set who = reassign WHERE NOT (who = ANY(fbsdids)); 33 | DELETE FROM namedqueries_link_in_footer; 34 | DELETE FROM namedquery_group_map; 35 | DELETE FROM namedqueries; 36 | DELETE FROM profile_search; 37 | DELETE FROM profile_setting; 38 | DELETE FROM profiles_activity; 39 | DELETE FROM profiles WHERE NOT (userid = ANY(fbsdids)); 40 | UPDATE quips SET userid = reassign WHERE NOT (userid = ANY(fbsdids)); 41 | DELETE FROM reports; 42 | DELETE FROM series_categories; 43 | DELETE FROM series_data; 44 | DELETE FROM series; 45 | UPDATE tag set user_id = reassign WHERE NOT (user_id = ANY(fbsdids)); 46 | DELETE FROM tokens; 47 | DELETE FROM user_group_map WHERE NOT (user_id = ANY(fbsdids)); 48 | DELETE FROM watch; 49 | DELETE FROM whine_schedules; 50 | DELETE FROM whine_queries; 51 | DELETE FROM whine_events; 52 | END $$; 53 | 54 | -------------------------------------------------------------------------------- /contrib/contrib_reorg-tools_movebugs.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | use strict; 3 | 4 | use Cwd 'abs_path'; 5 | use File::Basename; 6 | use FindBin; 7 | use lib "$FindBin::Bin/../.."; 8 | use lib "$FindBin::Bin/../../lib"; 9 | 10 | use Bugzilla; 11 | use Bugzilla::Constants; 12 | use Bugzilla::FlagType; 13 | use Bugzilla::Hook; 14 | use Bugzilla::Util; 15 | 16 | Bugzilla->usage_mode(USAGE_MODE_CMDLINE); 17 | 18 | if (scalar @ARGV < 4) { 19 | die < 21 | 22 | Eg. movebugs.pl mozilla.org bmo bugzilla.mozilla.org admin 23 | Will move all bugs in the mozilla.org:bmo component to the 24 | bugzilla.mozilla.org:admin component. 25 | 26 | The new product must have matching versions, milestones, and flags from the old 27 | product (will be validated by this script). 28 | USAGE 29 | } 30 | 31 | my ($old_product, $old_component, $new_product, $new_component) = @ARGV; 32 | 33 | my $dbh = Bugzilla->dbh; 34 | 35 | my $old_product_id = $dbh->selectrow_array( 36 | "SELECT id FROM products WHERE name=?", 37 | undef, $old_product); 38 | $old_product_id 39 | or die "Can't find product ID for '$old_product'.\n"; 40 | 41 | my $old_component_id = $dbh->selectrow_array( 42 | "SELECT id FROM components WHERE name=? AND product_id=?", 43 | undef, $old_component, $old_product_id); 44 | $old_component_id 45 | or die "Can't find component ID for '$old_component'.\n"; 46 | 47 | my $new_product_id = $dbh->selectrow_array( 48 | "SELECT id FROM products WHERE name=?", 49 | undef, $new_product); 50 | $new_product_id 51 | or die "Can't find product ID for '$new_product'.\n"; 52 | 53 | my $new_component_id = $dbh->selectrow_array( 54 | "SELECT id FROM components WHERE name=? AND product_id=?", 55 | undef, $new_component, $new_product_id); 56 | $new_component_id 57 | or die "Can't find component ID for '$new_component'.\n"; 58 | 59 | my $product_field_id = $dbh->selectrow_array( 60 | "SELECT id FROM fielddefs WHERE name = 'product'"); 61 | $product_field_id 62 | or die "Can't find field ID for 'product' field\n"; 63 | my $component_field_id = $dbh->selectrow_array( 64 | "SELECT id FROM fielddefs WHERE name = 'component'"); 65 | $component_field_id 66 | or die "Can't find field ID for 'component' field\n"; 67 | 68 | my $user_id = $dbh->selectrow_array( 69 | "SELECT userid FROM profiles WHERE login_name='nobody\@mozilla.org'"); 70 | $user_id 71 | or die "Can't find user ID for 'nobody\@mozilla.org'\n"; 72 | 73 | $dbh->bz_start_transaction(); 74 | 75 | # build list of bugs 76 | my $ra_ids = $dbh->selectcol_arrayref( 77 | "SELECT bug_id FROM bugs WHERE product_id=? AND component_id=?", 78 | undef, $old_product_id, $old_component_id); 79 | my $bug_count = scalar @$ra_ids; 80 | $bug_count 81 | or die "No bugs were found in '$old_component'\n"; 82 | my $where_sql = 'bug_id IN (' . join(',', @$ra_ids) . ')'; 83 | 84 | # check versions 85 | my @missing_versions; 86 | my $ra_versions = $dbh->selectcol_arrayref( 87 | "SELECT DISTINCT version FROM bugs WHERE $where_sql"); 88 | foreach my $version (@$ra_versions) { 89 | my $has_version = $dbh->selectrow_array( 90 | "SELECT 1 FROM versions WHERE product_id=? AND value=?", 91 | undef, $new_product_id, $version); 92 | push @missing_versions, $version unless $has_version; 93 | } 94 | 95 | # check milestones 96 | my @missing_milestones; 97 | my $ra_milestones = $dbh->selectcol_arrayref( 98 | "SELECT DISTINCT target_milestone FROM bugs WHERE $where_sql"); 99 | foreach my $milestone (@$ra_milestones) { 100 | my $has_milestone = $dbh->selectrow_array( 101 | "SELECT 1 FROM milestones WHERE product_id=? AND value=?", 102 | undef, $new_product_id, $milestone); 103 | push @missing_milestones, $milestone unless $has_milestone; 104 | } 105 | 106 | # check flags 107 | my @missing_flags; 108 | my $ra_old_types = $dbh->selectcol_arrayref( 109 | "SELECT DISTINCT type_id 110 | FROM flags 111 | INNER JOIN flagtypes ON flagtypes.id = flags.type_id 112 | WHERE $where_sql"); 113 | my $ra_new_types = 114 | Bugzilla::FlagType::match({ product_id => $new_product_id, 115 | component_id => $new_component_id }); 116 | foreach my $old_type (@$ra_old_types) { 117 | unless (grep { $_->id == $old_type } @$ra_new_types) { 118 | my $flagtype = Bugzilla::FlagType->new($old_type); 119 | push @missing_flags, $flagtype->name . ' (' . $flagtype->target_type . ')'; 120 | } 121 | } 122 | 123 | # show missing 124 | my $missing_error = ''; 125 | if (@missing_versions) { 126 | $missing_error .= "'$new_product' is missing the following version(s):\n " . 127 | join("\n ", @missing_versions) . "\n"; 128 | } 129 | if (@missing_milestones) { 130 | $missing_error .= "'$new_product' is missing the following milestone(s):\n " . 131 | join("\n ", @missing_milestones) . "\n"; 132 | } 133 | if (@missing_flags) { 134 | $missing_error .= "'$new_product'::'$new_component' is missing the following flag(s):\n " . 135 | join("\n ", @missing_flags) . "\n"; 136 | } 137 | die $missing_error if $missing_error; 138 | 139 | # confirmation 140 | print < to stop or to continue... 146 | EOF 147 | getc(); 148 | 149 | print "Moving $bug_count bugs from $old_product:$old_component to $new_product:$new_component\n"; 150 | 151 | # update bugs 152 | $dbh->do( 153 | "UPDATE bugs SET product_id=?, component_id=? WHERE $where_sql", 154 | undef, $new_product_id, $new_component_id); 155 | 156 | # touch bugs 157 | $dbh->do("UPDATE bugs SET delta_ts=NOW() WHERE $where_sql"); 158 | $dbh->do("UPDATE bugs SET lastdiffed=NOW() WHERE $where_sql"); 159 | 160 | # update bugs_activity 161 | $dbh->do( 162 | "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) 163 | SELECT bug_id, ?, delta_ts, ?, ?, ? FROM bugs WHERE $where_sql", 164 | undef, 165 | $user_id, $product_field_id, $old_product, $new_product); 166 | $dbh->do( 167 | "INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, added) 168 | SELECT bug_id, ?, delta_ts, ?, ?, ? FROM bugs WHERE $where_sql", 169 | undef, 170 | $user_id, $component_field_id, $old_component, $new_component); 171 | 172 | Bugzilla::Hook::process('reorg_move_bugs', { bug_ids => $ra_ids } ); 173 | 174 | $dbh->bz_commit_transaction(); 175 | 176 | foreach my $bug_id (@$ra_ids) { 177 | Bugzilla->memcached->clear({ table => 'bugs', id => $bug_id }); 178 | } 179 | 180 | -------------------------------------------------------------------------------- /contrib/contrib_reorg-tools_movecomponent.pl: -------------------------------------------------------------------------------- 1 | #!/usr/bin/perl -w 2 | # -*- Mode: perl; indent-tabs-mode: nil -*- 3 | # 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/ 8 | # 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. 13 | # 14 | # The Original Code is the Bugzilla Bug Tracking System. 15 | # 16 | # The Initial Developer of the Original Code is Netscape Communications 17 | # Corporation. Portions created by Netscape are 18 | # Copyright (C) 1998 Netscape Communications Corporation. All 19 | # Rights Reserved. 20 | # 21 | # Contributor(s): Gervase Markham 22 | 23 | # See also https://bugzilla.mozilla.org/show_bug.cgi?id=119569 24 | # 25 | 26 | use strict; 27 | 28 | use Cwd 'abs_path'; 29 | use File::Basename; 30 | BEGIN { 31 | my $root = abs_path(dirname(__FILE__) . '/../..'); 32 | chdir($root); 33 | } 34 | use lib qw(. lib); 35 | 36 | use Bugzilla; 37 | use Bugzilla::Constants; 38 | use Bugzilla::Hook; 39 | use Bugzilla::Util; 40 | 41 | sub usage() { 42 | print < 44 | 45 | E.g.: movecomponent.pl ReplicationEngine FoodReplicator SeaMonkey 46 | will move the component "SeaMonkey" from the product "ReplicationEngine" 47 | to the product "FoodReplicator". 48 | 49 | Important: You must make sure the milestones and versions of the bugs in the 50 | component are available in the new product. See syncmsandversions.pl. 51 | 52 | Pass in a true value for "doit" to make the database changes permament. 53 | USAGE 54 | 55 | exit(1); 56 | } 57 | 58 | ############################################################################# 59 | # MAIN CODE 60 | ############################################################################# 61 | 62 | # This is a pure command line script. 63 | Bugzilla->usage_mode(USAGE_MODE_CMDLINE); 64 | 65 | if (scalar @ARGV < 3) { 66 | usage(); 67 | exit(); 68 | } 69 | 70 | my ($oldproduct, $newproduct, $component, $doit) = @ARGV; 71 | 72 | my $dbh = Bugzilla->dbh; 73 | 74 | $dbh->{'AutoCommit'} = 0 unless $doit; # Turn off autocommit by default 75 | 76 | # Find product IDs 77 | my $oldprodid = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?", 78 | undef, $oldproduct); 79 | if (!$oldprodid) { 80 | print "Can't find product ID for '$oldproduct'.\n"; 81 | exit(1); 82 | } 83 | 84 | my $newprodid = $dbh->selectrow_array("SELECT id FROM products WHERE name = ?", 85 | undef, $newproduct); 86 | if (!$newprodid) { 87 | print "Can't find product ID for '$newproduct'.\n"; 88 | exit(1); 89 | } 90 | 91 | # Find component ID 92 | my $compid = $dbh->selectrow_array("SELECT id FROM components 93 | WHERE name = ? AND product_id = ?", 94 | undef, $component, $oldprodid); 95 | if (!$compid) { 96 | print "Can't find component ID for '$component' in product " . 97 | "'$oldproduct'.\n"; 98 | exit(1); 99 | } 100 | 101 | my $fieldid = $dbh->selectrow_array("SELECT id FROM fielddefs 102 | WHERE name = 'product'"); 103 | if (!$fieldid) { 104 | print "Can't find field ID for 'product' field!\n"; 105 | exit(1); 106 | } 107 | 108 | # check versions 109 | my @missing_versions; 110 | my $ra_versions = $dbh->selectcol_arrayref( 111 | "SELECT DISTINCT version FROM bugs WHERE component_id = ?", 112 | undef, $compid); 113 | foreach my $version (@$ra_versions) { 114 | my $has_version = $dbh->selectrow_array( 115 | "SELECT 1 FROM versions WHERE product_id = ? AND value = ?", 116 | undef, $newprodid, $version); 117 | push @missing_versions, $version unless $has_version; 118 | } 119 | 120 | # check milestones 121 | my @missing_milestones; 122 | my $ra_milestones = $dbh->selectcol_arrayref( 123 | "SELECT DISTINCT target_milestone FROM bugs WHERE component_id = ?", 124 | undef, $compid); 125 | foreach my $milestone (@$ra_milestones) { 126 | my $has_milestone = $dbh->selectrow_array( 127 | "SELECT 1 FROM milestones WHERE product_id=? AND value=?", 128 | undef, $newprodid, $milestone); 129 | push @missing_milestones, $milestone unless $has_milestone; 130 | } 131 | 132 | my $missing_error = ''; 133 | if (@missing_versions) { 134 | $missing_error .= "'$newproduct' is missing the following version(s):\n " . 135 | join("\n ", @missing_versions) . "\n"; 136 | } 137 | if (@missing_milestones) { 138 | $missing_error .= "'$newproduct' is missing the following milestone(s):\n " . 139 | join("\n ", @missing_milestones) . "\n"; 140 | } 141 | die $missing_error if $missing_error; 142 | 143 | # confirmation 144 | print < to stop or to continue... 150 | EOF 151 | getc(); 152 | 153 | print "Moving '$component' from '$oldproduct' to '$newproduct'...\n\n"; 154 | $dbh->bz_start_transaction() if $doit; 155 | 156 | my $ra_ids = $dbh->selectcol_arrayref( 157 | "SELECT bug_id FROM bugs WHERE product_id=? AND component_id=?", 158 | undef, $oldprodid, $compid); 159 | 160 | # Bugs table 161 | $dbh->do("UPDATE bugs SET product_id = ? WHERE component_id = ?", 162 | undef, 163 | ($newprodid, $compid)); 164 | 165 | # Flags tables 166 | $dbh->do("UPDATE flaginclusions SET product_id = ? WHERE component_id = ?", 167 | undef, 168 | ($newprodid, $compid)); 169 | 170 | $dbh->do("UPDATE flagexclusions SET product_id = ? WHERE component_id = ?", 171 | undef, 172 | ($newprodid, $compid)); 173 | 174 | # Components 175 | $dbh->do("UPDATE components SET product_id = ? WHERE id = ?", 176 | undef, 177 | ($newprodid, $compid)); 178 | 179 | # Mark bugs as touched 180 | $dbh->do("UPDATE bugs SET delta_ts = NOW() 181 | WHERE component_id = ?", undef, $compid); 182 | $dbh->do("UPDATE bugs SET lastdiffed = NOW() 183 | WHERE component_id = ?", undef, $compid); 184 | 185 | # Update bugs_activity 186 | my $userid = 1; # nobody@mozilla.org 187 | 188 | $dbh->do("INSERT INTO bugs_activity(bug_id, who, bug_when, fieldid, removed, 189 | added) 190 | SELECT bug_id, ?, delta_ts, ?, ?, ? 191 | FROM bugs WHERE component_id = ?", 192 | undef, 193 | ($userid, $fieldid, $oldproduct, $newproduct, $compid)); 194 | 195 | Bugzilla::Hook::process('reorg_move_bugs', { bug_ids => $ra_ids } ) if $doit; 196 | 197 | if ($doit) { 198 | $dbh->bz_commit_transaction(); 199 | 200 | # It's complex to determine which items now need to be flushed from memcached. 201 | # As this is expected to be a rare event, we just flush the entire cache. 202 | Bugzilla->memcached->clear_all(); 203 | } 204 | 205 | 206 | -------------------------------------------------------------------------------- /contrib/dbbackup.sh: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | PATH=$PATH:/usr/local/bin 4 | export PATH 5 | 6 | # Path to save the files to 7 | BACKUPPATH="/var/backups/bugzilla" 8 | 9 | # DB to save 10 | DBNAME="" 11 | 12 | # DB user 13 | DBUSER="" 14 | 15 | # Keep backups for how long? 16 | KEEPFOR="14d" 17 | 18 | # File to 19 | BACKUPFILE="$DBNAME-`date +\%Y-\%m-\%d-\%H_\%M_\%S`.backup" 20 | 21 | if [ ! -d $BACKUPPATH ]; then 22 | echo "[ERROR]: path $BACKUPPATH for storing the backup files does not exist" 1>&2 23 | exit 1; 24 | fi 25 | 26 | pg_dump --format=c --compress=5 -f $BACKUPPATH/$BACKUPFILE -U $DBUSER $DBNAME 27 | 28 | if [ $? -ne 0 ]; then 29 | echo "[ERROR]: creating a backup for $DBNAME to $BACKUPFILE failed!" 1>&2 30 | exit 1; 31 | fi 32 | 33 | find $BACKUPPATH -maxdepth 1 -mtime +$KEEPFOR -name "$DBNAME-*" -exec rm -f '{}' ';' 34 | 35 | -------------------------------------------------------------------------------- /contrib/delete_bug.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -w 2 | 3 | use strict; 4 | use warnings; 5 | use Bugzilla; 6 | use Bugzilla::Bug; 7 | use Bugzilla::Constants; 8 | 9 | my $count = $#ARGV + 1; 10 | if ($count < 1) { 11 | print("usage: delete_bug.pl id1 id2 id3 ...\n"); 12 | exit 1; 13 | } 14 | 15 | foreach my $id (@ARGV) { 16 | if ($id !~ /^\d+$/) { 17 | print("Argument '$id' is not a number\n"); 18 | exit 1; 19 | } 20 | } 21 | 22 | foreach my $id (@ARGV) { 23 | print("Deleting bug $id...\n"); 24 | my $bug = new Bugzilla::Bug($id); 25 | if ($bug->{error}) { 26 | my $err = $bug->{error}; 27 | print("Error: $err\n"); 28 | } else { 29 | $bug->remove_from_db(); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /contrib/rename-user.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -T 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | # This Source Code Form is "Incompatible With Secondary Licenses", as 7 | # defined by the Mozilla Public License, v. 2.0. 8 | 9 | use 5.10.1; 10 | use strict; 11 | use warnings; 12 | 13 | =head1 NAME 14 | 15 | rename-user.pl - Merge two user accounts. 16 | 17 | =head1 SYNOPSIS 18 | 19 | This script moves activity from one user account to another. 20 | Specify the two accounts on the command line, e.g.: 21 | 22 | ./rename-user.pl old_account@foo.com new_account@bar.com 23 | or: 24 | 25 | Notes: - the new account should not exist 26 | 27 | =cut 28 | 29 | use lib qw(. lib); 30 | 31 | use Bugzilla; 32 | use Bugzilla::Constants; 33 | use Bugzilla::Util; 34 | use Bugzilla::User; 35 | 36 | use Getopt::Long; 37 | use Pod::Usage; 38 | 39 | my $dbh = Bugzilla->dbh; 40 | 41 | # Display the help if called with --help or -?. 42 | my $help = 0; 43 | my $result = GetOptions("help|?" => \$help); 44 | pod2usage(0) if $help; 45 | 46 | 47 | # Make sure accounts were specified on the command line and exist. 48 | my $old = $ARGV[0] || die "You must specify an old user account.\n"; 49 | my $old_id; 50 | trick_taint($old); 51 | $old_id = $dbh->selectrow_array('SELECT userid FROM profiles 52 | WHERE login_name = ?', 53 | undef, $old); 54 | 55 | if ($old_id) { 56 | print "OK, old user account $old found; user ID: $old_id.\n"; 57 | } 58 | else { 59 | die "The old user account $old does not exist.\n"; 60 | } 61 | 62 | my $new = $ARGV[1] || die "You must specify a new user account.\n"; 63 | my $new_id; 64 | trick_taint($new); 65 | $new_id = $dbh->selectrow_array('SELECT userid FROM profiles 66 | WHERE login_name = ?', 67 | undef, $new); 68 | 69 | if ($new_id) { 70 | die "The new user account $new exists; user ID: $new_id.\n"; 71 | } 72 | 73 | $dbh->do('UPDATE profiles SET login_name = ? 74 | WHERE userid = ?', 75 | undef, ($new, $old_id)); 76 | 77 | # It's complex to determine which items now need to be flushed from memcached. 78 | # As user merge is expected to be a rare event, we just flush the entire cache 79 | # when users are merged. 80 | Bugzilla->memcached->clear_all(); 81 | 82 | print "Done.\n"; 83 | -------------------------------------------------------------------------------- /extensions/BFBSD/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::BFBSD; 2 | use strict; 3 | 4 | use constant NAME => 'BFBSD'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/BFBSD/Extension.pm: -------------------------------------------------------------------------------- 1 | # FreeBSD specific hooks for Bugzilla 2 | 3 | package Bugzilla::Extension::BFBSD; 4 | 5 | use strict; 6 | use warnings; 7 | use base qw(Bugzilla::Extension); 8 | 9 | use Bugzilla::Constants; 10 | use Bugzilla::Keyword; 11 | use Bugzilla::User; 12 | use Bugzilla::Extension::BFBSD::Helpers; 13 | 14 | our $VERSION = '0.1.0'; 15 | 16 | sub install_update_db { 17 | my ($self, $args) = @_; 18 | my $dbh = Bugzilla->dbh; 19 | 20 | my $kwd = new Bugzilla::Keyword({ name => "patch" }); 21 | if (!$kwd) { 22 | print("Creating 'patch' keyword ...\n"); 23 | $kwd = Bugzilla::Keyword->create({ 24 | name => "patch", 25 | description => "Contains a patch relevant to resolving the issue. May require testing, review or both.", 26 | }); 27 | } 28 | $kwd = new Bugzilla::Keyword({ name => "regression" }); 29 | if (!$kwd) { 30 | print("Creating 'regression' keyword ...\n"); 31 | $kwd = Bugzilla::Keyword->create({ 32 | name => "regression", 33 | description => "Describes an issue where a feature has stopped functioning as intended, and that previous worked.", 34 | }); 35 | } 36 | } 37 | 38 | sub bug_check_can_change_field { 39 | my ($self, $args) = @_; 40 | if ($args->{'field'} eq 'keywords') { 41 | my $user = Bugzilla->user; 42 | my $reporter = $args->{'bug'}->reporter->id; 43 | if (($user->id != $reporter) && 44 | !$user->in_group('editbugs', $args->{'bug'}->product_id)) { 45 | push(@{ $args->{'priv_results'} }, PRIVILEGES_REQUIRED_EMPOWERED); 46 | return; 47 | } 48 | } 49 | } 50 | 51 | sub auth_verify_methods { 52 | my ($self, $args) = @_; 53 | my $mods = $args->{'modules'}; 54 | if (exists $mods->{'FreeBSD'}) { 55 | $mods->{'FreeBSD'} = 'Bugzilla/Extension/BFBSD/Auth/Verify.pm'; 56 | } 57 | } 58 | 59 | sub config_modify_panels { 60 | my ($self, $args) = @_; 61 | my $panels = $args->{panels}; 62 | my $auth_params = $panels->{'auth'}->{params}; 63 | my ($verify_class) = grep($_->{name} eq 'user_verify_class', @$auth_params); 64 | push(@{ $verify_class->{choices} }, 'FreeBSD'); 65 | } 66 | 67 | sub bug_end_of_create { 68 | # Bug 196909 - Add $arch@freebsd.org for arch specific ports tickets 69 | my ($self, $args) = @_; 70 | my $bug = $args->{'bug'}; 71 | 72 | # Switch the user session 73 | my $curuser = switch_to_automation(); 74 | return if !defined($curuser); 75 | 76 | # Bug 196909 - add $arch CCs for ports bugs with 77 | # platform != (amd64, i386) 78 | # We only add CCs, if it is a individual port bug 79 | if ($bug->product eq PRODUCT_PORTS && 80 | $bug->component eq COMPONENT_PORTS) { 81 | if ($bug->rep_platform ne "amd64" && $bug->rep_platform ne "i386" && 82 | $bug->rep_platform ne "Any") { 83 | 84 | my $archuser = sprintf("%s\@FreeBSD.org", 85 | $bug->rep_platform); 86 | my $user = get_user($archuser, 1); 87 | if ($user) { 88 | $bug->add_cc($user); 89 | }; 90 | } 91 | } 92 | Bugzilla->set_user($curuser); 93 | } 94 | 95 | # Auto-correct mimetypes for better usability 96 | # users prefer to view plain version of file in the browser 97 | # not download it 98 | sub _adjust_mime_type { 99 | my $attachment = shift; 100 | 101 | if (!defined(Bugzilla->input_params->{contenttypemethod}) || 102 | (Bugzilla->input_params->{contenttypemethod} eq 'autodetect')) { 103 | my $mimetype = $attachment->{mimetype}; 104 | if (($mimetype eq 'application/shar') 105 | || ($mimetype eq 'application/x-shar')) { 106 | $attachment->{mimetype} = 'text/plain'; 107 | } 108 | elsif (($mimetype eq 'text/x-patch') 109 | || ($mimetype eq 'text/x-diff')) { 110 | $attachment->{mimetype} = 'text/plain'; 111 | $attachment->{ispatch} = 1; 112 | } 113 | elsif ($mimetype =~ m#text/.*#) { 114 | $attachment->{mimetype} = 'text/plain'; 115 | } 116 | } 117 | } 118 | 119 | sub object_end_of_create { 120 | my ($self, $args) = @_; 121 | my $class = $args->{'class'}; 122 | my $object = $args->{'object'}; 123 | if ($class->isa('Bugzilla::Attachment')) { 124 | _adjust_mime_type($object); 125 | } 126 | } 127 | 128 | __PACKAGE__->NAME; 129 | -------------------------------------------------------------------------------- /extensions/BFBSD/lib/Auth/Verify.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::BFBSD::Auth::Verify; 2 | 3 | use strict; 4 | use warnings; 5 | use base qw(Bugzilla::Auth::Verify); 6 | use Bugzilla::Auth::Verify::LDAP; 7 | use Bugzilla::Constants; 8 | 9 | our @ISA = qw(Bugzilla::Auth::Verify::LDAP); 10 | 11 | sub check_credentials { 12 | my ($self, $params) = @_; 13 | 14 | if ($params->{username} =~ /.+\@FreeBSD\.org$/i) { 15 | $params->{username} =~ s/\@.+$//; 16 | } 17 | $params = Bugzilla::Auth::Verify::LDAP::check_credentials($self, $params); 18 | if ($params->{failure}) { 19 | return $params; 20 | } 21 | $params->{bz_username} = $params->{username} . '@freebsd.org'; 22 | utf8::decode($params->{realname}) if (defined($params->{realname})); 23 | return $params; 24 | } 25 | 26 | 1; 27 | 28 | -------------------------------------------------------------------------------- /extensions/BFBSD/lib/Helpers.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::BFBSD::Helpers; 2 | 3 | use strict; 4 | use Bugzilla; 5 | use Bugzilla::User; 6 | 7 | use base qw(Exporter); 8 | 9 | our @EXPORT = qw( 10 | no_maintainer get_user ports_product ports_component 11 | switch_to_automation 12 | 13 | UID_AUTOMATION 14 | PRODUCT_PORTS 15 | COMPONENT_PORTS 16 | ); 17 | 18 | use constant { 19 | UID_AUTOMATION => "bugzilla\@FreeBSD.org", 20 | PRODUCT_PORTS => "Ports & Packages", 21 | COMPONENT_PORTS => "Individual Port(s)" 22 | }; 23 | 24 | sub ports_product { 25 | return PRODUCT_PORTS 26 | } 27 | 28 | sub ports_component { 29 | return COMPONENT_PORTS 30 | } 31 | 32 | sub no_maintainer { 33 | my $maintainer = shift(); 34 | if (lc($maintainer) eq "ports\@freebsd.org") { 35 | return 1; 36 | } 37 | return 0; 38 | } 39 | 40 | sub get_user { 41 | my ($name, $enabledonly) = @_; 42 | my $uid = login_to_id($name); 43 | if (!$uid) { 44 | warn("No user found for $name"); 45 | return; 46 | } 47 | my $user = new Bugzilla::User($uid); 48 | if ($enabledonly) { 49 | if (!$user->is_enabled) { 50 | warn("Found user $name is not enabled in Bugzilla"); 51 | return; 52 | } 53 | } 54 | return $user; 55 | } 56 | 57 | sub switch_to_automation { 58 | # Switch the user session 59 | my $autoid = login_to_id(UID_AUTOMATION); 60 | if (!$autoid) { 61 | warn("Automation user does not exist"); 62 | return; 63 | } 64 | my $curuser = Bugzilla->user; 65 | Bugzilla->set_user(new Bugzilla::User($autoid)); 66 | return $curuser; 67 | }; 68 | 69 | 1; 70 | -------------------------------------------------------------------------------- /extensions/BFBSD/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl: -------------------------------------------------------------------------------- 1 | [% IF user.id %] 2 | 3 | Reporter: 4 | 5 | 6 | [% bug.reporter.identity FILTER html %] 7 | 8 | 9 | [%- END -%] 10 | -------------------------------------------------------------------------------- /extensions/Dashboard/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Dashboard; 2 | use strict; 3 | 4 | use constant NAME => 'Dashboard'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/Dashboard/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Dashboard; 2 | use strict; 3 | use base qw(Bugzilla::Extension); 4 | 5 | use Bugzilla; 6 | use Bugzilla::Constants; 7 | use Bugzilla::Error; 8 | use Bugzilla::Util; 9 | use Bugzilla::Extension::Dashboard::Util; 10 | 11 | our $VERSION = '0.1.0'; 12 | 13 | use constant { 14 | MAXDAYS => 366, 15 | IDLEDAYS => 30, 16 | DEFDAYS => 14 17 | 18 | }; 19 | 20 | sub page_before_template { 21 | my ($self, $args) = @_; 22 | my $page = $args->{page_id}; 23 | my $vars = $args->{vars}; 24 | 25 | if ($page ne "dashboard.html") { 26 | # Do not do anything, if the currently requested page is not 27 | # the dashboard. 28 | return; 29 | } 30 | # The dashboard requires a login for proper rights. 31 | Bugzilla->login(LOGIN_REQUIRED); 32 | 33 | my $cgi = Bugzilla->cgi; 34 | my $user = Bugzilla->user; 35 | my $urlbase = Bugzilla->params->{urlbase}; 36 | my $days = DEFDAYS; 37 | 38 | # Ensure that the &days=XX GET parameter is valid 39 | if (defined($cgi->param("days")) && $cgi->param('days') ne "") { 40 | $days = $cgi->param("days"); 41 | detaint_natural($days) or ThrowUserError("days_type_number"); 42 | } 43 | ($days < MAXDAYS) or ThrowUserError("days_max_days"); 44 | 45 | # Create some statistics about the total number of open bugs, new bugs 46 | # within the last XX days and closed bugs for the last XX days. 47 | my ($tncount, $tocount, $tccount) = (0, 0, 0); 48 | my @products; 49 | my $productlist = $user->get_selectable_products(); 50 | foreach my $product (@$productlist) { 51 | my ($ncrit, $ncount, $nbugs) = new_bugs($product, $days); 52 | my ($ccrit, $ccount, $cbugs) = closed_bugs($product, $days); 53 | my ($ocrit, $ocount, $obugs) = open_bugs($product); 54 | 55 | $tncount += $ncount; 56 | $tocount += $ocount; 57 | $tccount += $ccount; 58 | 59 | push(@products, { 60 | "name" => $product->name, 61 | "id" => $product->id, 62 | "total" => $ocount, 63 | "new" => $ncount, 64 | "ncrit" => $ncrit, 65 | "closed" => $ccount, 66 | "ccrit" => $ccrit 67 | }); 68 | } 69 | $vars->{products} = \@products; 70 | 71 | my $tncrit = new_bugs(undef, $days, 1); 72 | my $tccrit = closed_bugs(undef, $days, 1); 73 | 74 | $vars->{totals} = { 75 | "total" => $tocount, 76 | "new" => $tncount, 77 | "ncrit" => $tncrit, 78 | "closed" => $tccount, 79 | "ccrit" => $tccrit, 80 | }; 81 | $vars->{days} = $days; 82 | # Get useful queries 83 | $vars->{queries} = _predefined_queries($user, $urlbase); 84 | $vars->{userqueries} = _user_queries($user, $urlbase); 85 | } 86 | 87 | sub _user_queries { 88 | my ($user, $urlbase) = @_; 89 | my $result = (); 90 | 91 | my ($criteria, $count, $buglist) = flags_requestee(); 92 | $result->{requestee} = $buglist; 93 | 94 | ($criteria, $count, $buglist) = flags_setter(); 95 | $result->{setter} = $buglist; 96 | 97 | return $result; 98 | } 99 | 100 | sub _predefined_queries { 101 | my ($user, $urlbase) = @_; 102 | my @result; 103 | 104 | my @queries = ( 105 | { 106 | "desc" => "Bugs waiting for feedback (flags) for more than " . DEFDAYS . " days", 107 | "func" => \&missing_feedback, 108 | "args" => DEFDAYS 109 | }, 110 | { 111 | "desc" => "Idle bugs, for which nothing happend for more than " . IDLEDAYS . " days", 112 | "func" => \&idle_bugs, 113 | "args" => IDLEDAYS 114 | }, 115 | { 116 | "desc" => "Bugs, that need to be backported from current/head (MFC)", 117 | "func" => \&mfc_bugs, 118 | "args" => undef 119 | }, 120 | { 121 | "desc" => "Ports bugs that have not been looked at yet", 122 | "func" => \&new_ports_bugs, 123 | "args" => undef 124 | }, 125 | # 20231130 we have moved away from the "patch-ready" convention. 126 | # { 127 | # "desc" => "Ports bugs, that are ready to be taken by a committer", 128 | # "func" => \&commit_ports_bugs, 129 | # "args" => undef 130 | # }, 131 | ); 132 | foreach my $entry (@queries) { 133 | my ($criteria, $count, $buglist) = $entry->{func}($entry->{args}); 134 | push(@result, { 135 | "desc" => $entry->{desc}, 136 | "count" => $count, 137 | "crit" => $criteria 138 | }); 139 | } 140 | return \@result; 141 | } 142 | 143 | __PACKAGE__->NAME; 144 | -------------------------------------------------------------------------------- /extensions/Dashboard/lib/Util.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Dashboard::Util; 2 | 3 | use strict; 4 | use POSIX qw(strftime); 5 | use Bugzilla; 6 | use Bugzilla::Flag; 7 | use Bugzilla::Search; 8 | use base qw(Exporter); 9 | 10 | use constant { 11 | SEC_PER_DAY => 86400 12 | }; 13 | 14 | our @EXPORT = qw( 15 | open_bugs new_bugs closed_bugs missing_feedback idle_bugs 16 | new_ports_bugs commit_ports_bugs mfc_bugs flags_requestee 17 | flags_setter 18 | ); 19 | 20 | sub _search { 21 | my ($criteria, $fields) = @_; 22 | 23 | if (!$fields) { 24 | $fields = [ "bug_id" ]; 25 | } 26 | 27 | my $search = new Bugzilla::Search( 28 | "fields" => $fields, 29 | "params" => $criteria, 30 | "user" => Bugzilla->user, 31 | "allow_unlimited" => 1 32 | ); 33 | my $result = $search->data; 34 | return ($criteria, scalar(@$result), $result); 35 | } 36 | 37 | sub open_bugs { 38 | my $product = shift(); 39 | my $critonly = shift(); 40 | 41 | my %criteria = ( 42 | "bug_status" => "__open__", 43 | ); 44 | if (defined($product)) { 45 | $criteria{"product"} = $product->name; 46 | } 47 | if ($critonly) { 48 | return \%criteria; 49 | } else { 50 | return _search(\%criteria); 51 | } 52 | } 53 | 54 | sub new_bugs { 55 | my ($product, $days, $critonly) = @_; 56 | 57 | my $back = time() - ($days * SEC_PER_DAY); 58 | my $ts = strftime("%Y-%m-%d", localtime($back)); 59 | 60 | my %criteria = ( 61 | "f1" => "creation_ts", 62 | "o1" => "greaterthaneq", 63 | "v1" => $ts 64 | ); 65 | if (defined($product)) { 66 | $criteria{"product"} = $product->name; 67 | } 68 | if ($critonly) { 69 | return \%criteria; 70 | } else { 71 | return _search(\%criteria); 72 | } 73 | } 74 | 75 | sub closed_bugs { 76 | my ($product, $days, $critonly) = @_; 77 | 78 | my $back = time() - ($days * 24 * 60 * 60); 79 | my $ts = strftime("%Y-%m-%d", localtime($back)); 80 | 81 | my %criteria = ( 82 | "bug_status" => "__closed__", 83 | "f1" => "delta_ts", 84 | "o1" => "greaterthaneq", 85 | "v1" => $ts 86 | ); 87 | if (defined($product)) { 88 | $criteria{"product"} = $product->name; 89 | } 90 | if ($critonly) { 91 | return \%criteria; 92 | } else { 93 | return _search(\%criteria); 94 | } 95 | } 96 | 97 | sub missing_feedback { 98 | my ($days, $critonly) = @_; 99 | 100 | my %criteria = ( 101 | "bug_status" => "__open__", 102 | "f1" => "flagtypes.name", 103 | "o1" => "substring", 104 | "v1" => "?", 105 | "f2" => "flagtypes.name", 106 | "o2" => "changedbefore", 107 | "v2" => "" . $days . "d" 108 | ); 109 | if ($critonly) { 110 | return \%criteria; 111 | } else { 112 | return _search(\%criteria); 113 | } 114 | } 115 | 116 | sub idle_bugs { 117 | my ($days, $critonly) = @_; 118 | 119 | my %criteria = ( 120 | "bug_status" => "__open__", 121 | "f2" => "days_elapsed", 122 | "o2" => "greaterthan", 123 | "v2" => "" . $days 124 | ); 125 | if ($critonly) { 126 | return \%criteria; 127 | } else { 128 | return _search(\%criteria); 129 | } 130 | } 131 | 132 | sub new_ports_bugs { 133 | my $critonly = shift(); 134 | 135 | my %criteria = ( 136 | "bug_status" => "New", 137 | "email1" => "ports-bugs\@FreeBSD.org", 138 | "emailassigned_to1" => "1", 139 | "emailtype1" => "exact", 140 | "product" => "Ports & Packages", 141 | # "f1" => "longdescs.count", 142 | # "o1" => "equals", 143 | # "v1" => "1" 144 | ); 145 | if ($critonly) { 146 | return \%criteria; 147 | } else { 148 | return _search(\%criteria); 149 | } 150 | } 151 | 152 | sub commit_ports_bugs { 153 | my $critonly = shift(); 154 | 155 | my %criteria = ( 156 | "email1" => "ports-bugs\@FreeBSD.org", 157 | "emailassigned_to1" => "1", 158 | "emailtype1" => "exact", 159 | "f1" => "keywords", 160 | "o1" => "substring", 161 | "v1" => "patch-ready", 162 | "product" => "Ports & Packages", 163 | "bug_status" => "__open__", 164 | ); 165 | if ($critonly) { 166 | return \%criteria; 167 | } else { 168 | return _search(\%criteria); 169 | } 170 | } 171 | 172 | sub mfc_bugs { 173 | my $critonly = shift(); 174 | 175 | my %criteria = ( 176 | "f1" => "flagtypes.name", 177 | "o1" => "anywordssubstr", 178 | "v1" => "mfc-stable13? mfc-stable14? merge-quarterly?", 179 | "bug_status" => "__open__", 180 | ); 181 | if ($critonly) { 182 | return \%criteria; 183 | } else { 184 | return _search(\%criteria); 185 | } 186 | } 187 | 188 | sub flags_requestee { 189 | my $critonly = shift(); 190 | 191 | my $user = Bugzilla->user; 192 | my %criteria = ( 193 | "f1" => "requestees.login_name", 194 | "o1" => "equals", 195 | "v1" => $user->login, 196 | "bug_status" => "__open__", 197 | ); 198 | if ($critonly) { 199 | return \%criteria; 200 | } 201 | 202 | # We can't request the login_names, since the SQL query blows up 203 | # then. Let's do it manually and push all in a simple array. 204 | my @bugs; 205 | my $matchedbugs = Bugzilla::Flag->match({ 206 | status => '?', 207 | requestee_id => $user->id 208 | }); 209 | my %bugids = map{$_->bug_id, $_} @$matchedbugs; 210 | 211 | # $matchedbugs won't necessarily contain only bugs, which 212 | # match the user preferences, visibility or bug status, 213 | # let's sync them with the Bugzilla::Search() result. 214 | 215 | my ($crit, $count, $ids) = _search( 216 | \%criteria, ["bug_id", "short_desc"]); 217 | 218 | foreach my $id (@$ids) { 219 | my ($bug_id, $desc) = @$id; 220 | next if (!$bugids{$bug_id}); 221 | 222 | my $flag = $bugids{$bug_id}; 223 | push(@bugs, { 224 | "id" => $flag->bug_id, 225 | "desc" => $desc, 226 | "flag" => $flag->type->name, 227 | "requester" => $flag->setter->login, 228 | "requestee" => $flag->requestee ? $flag->requestee->login : '', 229 | }); 230 | } 231 | return (\%criteria, scalar(@bugs), \@bugs); 232 | } 233 | 234 | sub flags_setter { 235 | my $critonly = shift(); 236 | 237 | my $user = Bugzilla->user; 238 | my %criteria = ( 239 | "f1" => "setters.login_name", 240 | "o1" => "equals", 241 | "v1" => $user->login, 242 | "bug_status" => "__open__", 243 | ); 244 | if ($critonly) { 245 | return \%criteria; 246 | } 247 | 248 | # We can't request the login_names, since the SQL query blows up 249 | # then. Let's do it manually and push all in a simple array. 250 | my @bugs; 251 | my $matchedbugs = Bugzilla::Flag->match({ 252 | status => '?', 253 | setter_id => $user->id 254 | }); 255 | my %bugids = map{$_->bug_id, $_} @$matchedbugs; 256 | 257 | # $matchedbugs won't necessarily contain only bugs, which 258 | # match the user preferences, visibility or bug status, 259 | # let's sync them with the Bugzilla::Search() result. 260 | 261 | my ($crit, $count, $ids) = _search( 262 | \%criteria, ["bug_id", "short_desc"]); 263 | foreach my $id (@$ids) { 264 | my ($bug_id, $desc) = @$id; 265 | next if (!$bugids{$bug_id}); 266 | 267 | my $flag = $bugids{$bug_id}; 268 | push(@bugs, { 269 | "id" => $flag->bug_id, 270 | "desc" => $desc, 271 | "flag" => $flag->type->name, 272 | "requester" => $flag->setter->login, 273 | "requestee" => $flag->requestee ? $flag->requestee->login : '', 274 | }); 275 | } 276 | return (\%criteria, scalar(@bugs), \@bugs); 277 | } 278 | -------------------------------------------------------------------------------- /extensions/Dashboard/template/en/default/hook/global/common-links-fbsd-links.html.tmpl: -------------------------------------------------------------------------------- 1 | [% IF user.login %] 2 |
  • 3 | | 4 | Dashboard 5 |
  • 6 | [% END %] 7 | -------------------------------------------------------------------------------- /extensions/Dashboard/template/en/default/hook/global/user-error-errors.html.tmpl: -------------------------------------------------------------------------------- 1 | [% IF error == "days_type_number" %] 2 | [% title = "Invalid Days Type" %] 3 | Invalid type for the days range. 4 | [% END %] 5 | [% IF error == "days_max_days" %] 6 | [% title = "Invalid Days Range" %] 7 | The maximum range for days is 365. 8 | [% END %] 9 | -------------------------------------------------------------------------------- /extensions/Dashboard/template/en/default/pages/dashboard.html.tmpl: -------------------------------------------------------------------------------- 1 | [% PROCESS global/variables.none.tmpl %] 2 | [% PROCESS global/header.html.tmpl 3 | title = "Dashboard" 4 | style_urls = [ "extensions/Dashboard/web/style.css", 5 | "skins/standard/buglist.css"] 6 | %] 7 | 8 | [% optdays = [ 1, 2, 3, 4, 5, 6, 7, 14, 30, 60, 90, 120, 180, 240, 300, 365 ] %] 9 | 10 |
    11 |
    12 |
    13 |
    14 |

    Bug Summary

    15 |

    The bug summary provides a brief overview about the most recently 16 | opened and closed bugs for the project and a per-product basis. 17 |

    18 |
    19 | 20 |

    Day range: 21 | 30 | 31 |

    32 |
    33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | [% FOREACH p IN products %] 44 | 45 | 49 | 56 | 67 | 78 | 79 | [% END %] 80 | 81 | 82 | 85 | 96 | 108 | 109 | 110 |
    ProductTotal OpenNew in the last [% days FILTER html %] daysClosed in the last [% days FILTER html %] days
    46 | 47 | [% p.name FILTER html %] 48 | 50 | [% IF p.total != 0 %] 51 | [% p.total FILTER html %] 52 | [% ELSE %] 53 | 0 54 | [% END %] 55 | 57 | [% IF p.new != 0 %] 58 | [% p.new FILTER html %] 63 | [% ELSE %] 64 | 0 65 | [% END %] 66 | 68 | [% IF p.closed != 0 %] 69 | [% p.closed FILTER html %] 74 | [% ELSE %] 75 | 0 76 | [% END %] 77 |
    Total numbers 83 | [% totals.total %] 84 | 86 | [% IF totals.open != 0 %] 87 | [% totals.new FILTER html %] 92 | [% ELSE %] 93 | 0 94 | [% END %] 95 | 97 | [% IF totals.closed != 0 %] 98 | [% totals.closed FILTER html %] 103 | 104 | [% ELSE %] 105 | 0 106 | [% END %] 107 |
    111 |
    112 |
    113 |

    Useful Queries

    114 |

    Some pre-defined queries to find bugs matching certain criteria. 115 | More can be found 116 | on the wiki.

    117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | [% FOREACH q IN queries %] 126 | 127 | 128 | 133 | 134 | [% END %] 135 | 136 |
    QueryBug Count
    [% q.desc %][% q.count %]
    137 |
    138 |
    139 | 140 |
    141 |

    Requests to Answer

    142 |

    This is a list of bugs containing requests, which you should react on.

    143 | 144 | 145 | 146 | 147 | 148 | 149 | 150 | 151 | 152 | 153 | [% FOREACH bug IN userqueries.requestee %] 154 | 155 | 156 | 157 | 158 | 159 | 160 | [% END %] 161 | 162 |
    IdDescriptionFlagRequester
    [% bug.id %][% bug.desc %][% bug.flag %][% bug.requester %]
    163 |

    Requests Made

    164 |

    This is a list of bugs containing requests, you made and which currently 165 | wait for someone else to react on.

    166 | 167 | 168 | 169 | 170 | 171 | 172 | 173 | 174 | 175 | 176 | [% FOREACH bug IN userqueries.setter %] 177 | [% count = loop.count() %] 178 | 179 | 180 | 181 | 182 | 183 | 184 | [% END %] 185 | 186 |
    IdDescriptionFlagRequestee
    [% bug.id %][% bug.desc %][% bug.flag %][% bug.requestee %]
    187 |
    188 | 189 |
    190 |
    191 | 192 |

    193 | If you should have trouble with the searches or numbers provided, or 194 | if you would like to see other predefined searches and metrics, 195 | please file 196 | a report. 197 |

    198 |
    199 | [% PROCESS global/footer.html.tmpl %] 200 | -------------------------------------------------------------------------------- /extensions/Dashboard/web/style.css: -------------------------------------------------------------------------------- 1 | 2 | 3 | .dashboard { 4 | background-color: #EEE; 5 | border: 1px solid #DDD; 6 | border-collapse: collapse; 7 | } 8 | 9 | .dashboard { 10 | text-align: right; 11 | } 12 | 13 | .dashboard tr th { 14 | padding: 1em; 15 | border: 1px solid #DDD; 16 | } 17 | .dashboard tr td { 18 | padding: 1em; 19 | border: 1px solid #DDD; 20 | } 21 | .left { 22 | text-align: left; 23 | } 24 | 25 | #dayselect { 26 | width: 4em; 27 | } 28 | 29 | .product { 30 | font-weight: bold; 31 | } 32 | 33 | @media (min-width: 64rem) { 34 | div#left { 35 | float: left; 36 | width: 50%; 37 | margin-right: 1%; 38 | } 39 | 40 | div#requests { 41 | float: right; 42 | width: 49%; 43 | } 44 | } 45 | 46 | .requests { 47 | background-color: #EEE; 48 | border: 1px solid #DDD; 49 | border-collapse: collapse; 50 | width: 100%; 51 | } 52 | .requests tr th { 53 | padding: .5em; 54 | border: 1px solid #DDD; 55 | } 56 | .requests tr th { 57 | white-space: nowrap; 58 | } 59 | .requests tr th.expand { 60 | width: 99%; 61 | } 62 | 63 | .requests tr td { 64 | padding: .5em; 65 | white-space: nowrap; 66 | } 67 | 68 | .header { 69 | text-align: left; 70 | font-weight: bold; 71 | } 72 | -------------------------------------------------------------------------------- /extensions/FBSDAutoAssign/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::FBSDAutoAssign; 2 | use strict; 3 | 4 | use constant NAME => 'FBSDAutoAssign'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/FBSDAutoAssign/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::FBSDAutoAssign; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use base qw(Bugzilla::Extension); 7 | 8 | use Bugzilla::Comment; 9 | use Bugzilla::Field; 10 | use Bugzilla::FlagType; 11 | use Bugzilla::Flag; 12 | use Bugzilla::Mailer; 13 | use Bugzilla::User; 14 | 15 | use Bugzilla::Extension::BFBSD::Helpers; 16 | 17 | use constant { 18 | PORTSDIR => "/home/ports", 19 | INDEX => "INDEX", 20 | # Bug 196372 - make comments optional 21 | DOCOMMENT => 0, 22 | REASSIGN => 1 23 | }; 24 | 25 | our $VERSION = '0.3.0'; 26 | 27 | sub install_update_db { 28 | my ($self, $args) = @_; 29 | my $dbh = Bugzilla->dbh; 30 | 31 | print("Checking for ports directory... "); 32 | if (-d PORTSDIR) { 33 | print("found!\n"); 34 | } else { 35 | print("NOT FOUND - please create it at " . PORTSDIR . "!\n"); 36 | } 37 | 38 | if (@{ Bugzilla::FlagType::match({ name => 'maintainer-feedback' }) }) { 39 | return; 40 | } 41 | 42 | print("Creating maintainer-feedback flag ...\n"); 43 | 44 | my $flagtype = Bugzilla::FlagType->create({ 45 | name => 'maintainer-feedback', 46 | description => "Set this flag, if you want to request information from the maintainer", 47 | target_type => 'bug', 48 | cc_list => '', 49 | sortkey => 1, 50 | is_active => 1, 51 | is_requestable => 1, 52 | is_requesteeble => 1, 53 | is_multiplicable => 0, 54 | request_group => '', 55 | grant_group => '', 56 | inclusions => ['0:0'], 57 | exclusions => [], 58 | }); 59 | } 60 | 61 | sub bug_end_of_create { 62 | my ($self, $args) = @_; 63 | my $bug = $args->{'bug'}; 64 | 65 | # We only add CCs, if it is a individual port bug 66 | if ($bug->product ne PRODUCT_PORTS || 67 | $bug->component ne COMPONENT_PORTS) { 68 | return; 69 | } 70 | 71 | my $flag_exprun; 72 | my $flagtypes = Bugzilla::FlagType::match( 73 | { name => 'exp-run' }); 74 | if (scalar(@$flagtypes) == 1) { 75 | $flag_exprun = @{$flagtypes}[0]; 76 | } 77 | 78 | if (defined($flag_exprun) && (lc($bug->assigned_to->login) eq 'portmgr@freebsd.org')) { 79 | my $vars = {}; 80 | my ($flags, $new_flags) = Bugzilla::Flag->extract_flags_from_cgi($bug, undef, $vars, SKIP_REQUESTEE_ON_ERROR); 81 | if (grep {$_->{type_id} == $flag_exprun->id} @{$new_flags}) { 82 | return; 83 | } 84 | } 85 | 86 | my @foundports = (); 87 | 88 | # Is it a port patch in summary matching 89 | # (/usr/ports/)?([A-Za-z0-9_-]/[A-Za-z0-9_-])? 90 | my @res = ($bug->short_desc =~ /(?:^|[:,\[\s+])(?:\/usr\/ports\/)?([\w\-]+\/[\w\-\.]+)(?:@[\w\-\.]+)?(?:[:,\]\s+]|$)/g); 91 | my $sd = $bug->short_desc; 92 | my $x = join ":", @res; 93 | if (@res && scalar(@res) > 0) { 94 | # warn("Found ports in summary: @res"); 95 | push(@foundports, @res); 96 | } 97 | 98 | if (scalar(@foundports) == 0) { 99 | # Did not find a port in subject 100 | # Is it a port in the description matching 101 | # (/usr/ports/)?([A-Za-z0-9_-]/[A-Za-z0-9_-])? 102 | my $first = $bug->comments->[0]->body; 103 | @res = ($first =~ /(?:^|[:,\s+])(?:\/usr\/ports\/)?([\w\-]+\/[\w\-\.]+)(?:@[\w\-\.]+)?(?:[:,\s+]|$)/g); 104 | if (@res && scalar(@res) > 0) { 105 | # warn("Found ports in description: @res"); 106 | push(@foundports, @res); 107 | } 108 | } 109 | # Remove duplicate entries. 110 | my %hashed = map{$_, 1} @foundports; 111 | @foundports = keys(%hashed); 112 | 113 | # Add the maintainers of the affected ports to the CC. If there is 114 | # only one person, add a feedback request for that person and 115 | # optionally assign (if it is a committer), otherwise set all into 116 | # CC. 117 | 118 | my @maintainers = (); 119 | my @categories = (); 120 | foreach my $port (@foundports) { 121 | my $maintainer = _get_maintainer($port); 122 | if ($maintainer) { 123 | push(@maintainers, $maintainer); 124 | push(@categories, $port =~ /^([\w\-]+)\/[\w\-\.]+$/g); 125 | } 126 | } 127 | 128 | # Remove duplicate entries 129 | %hashed = map{$_, 1} @maintainers; 130 | @maintainers = keys(%hashed); 131 | %hashed = map{$_, 1} @categories; 132 | @categories = keys(%hashed); 133 | 134 | _update_bug($bug, \@maintainers, \@categories); 135 | } 136 | 137 | sub _update_bug { 138 | my ($bug, $maintainers, $categories) = @_; 139 | 140 | # Switch the user session 141 | my $curuser = switch_to_automation(); 142 | return if !defined($curuser); 143 | 144 | # Only one maintainer? 145 | if (scalar(@$maintainers) == 1) { 146 | my $maintainer = @$maintainers[0]; 147 | if (no_maintainer($maintainer)) { 148 | Bugzilla->set_user($curuser); 149 | return; 150 | } 151 | my $user = get_user($maintainer, 1); 152 | if (!$user) { 153 | # Maintainer not registered or deactivated, send a mail. 154 | _send_mail_to($maintainer, $bug); 155 | $bug->add_comment("Maintainer informed via mail"); 156 | Bugzilla->set_user($curuser); 157 | return; 158 | } 159 | if ($curuser->id == $user->id) { 160 | # Maintainer updates should not ask the user for feedback. 161 | Bugzilla->set_user($curuser); 162 | return; 163 | } 164 | 165 | my $flag_feedback; 166 | my $flagtypes = Bugzilla::FlagType::match( 167 | { name => 'maintainer-feedback' }); 168 | if (scalar(@$flagtypes) == 1) { 169 | $flag_feedback = @{$flagtypes}[0]; 170 | } 171 | if (!$flag_feedback) { 172 | warn("maintainer-feedback flag not found"); 173 | } else { 174 | my (@oldflags, @newflags); 175 | push(@newflags, { type_id => $flag_feedback->id, 176 | status => "?", 177 | requestee => $user->login 178 | }); 179 | $bug->set_flags(\@oldflags, \@newflags); 180 | } 181 | if (REASSIGN != 0 && $user->login =~ /\@freebsd\.org$/i) { 182 | my $name = $user->login; 183 | $bug->set_assigned_to($user); 184 | # since we set the maintainer-feedback? flag, the default 185 | # behaviour of bugzilla is to add the user as CC. 186 | # We do not need that on reassignments 187 | $bug->remove_cc($user); 188 | if (DOCOMMENT != 0) { 189 | $bug->add_comment("Auto-assigned to maintainer $name"); 190 | } 191 | } else { 192 | $bug->add_cc($user); 193 | if (DOCOMMENT != 0) { 194 | $bug->add_comment("Maintainer CC'd"); 195 | } 196 | } 197 | } else { 198 | my $someoneccd = 0; 199 | foreach my $maintainer (@$maintainers) { 200 | next if no_maintainer($maintainer); 201 | 202 | my $user = get_user($maintainer, 1); 203 | if ($user) { 204 | if ($curuser->id != $user->id) { 205 | $bug->add_cc($user); 206 | $someoneccd = 1; 207 | } 208 | } else { 209 | # User not registered or deactivated, we won't send 210 | # mails to them. 211 | } 212 | } 213 | if (DOCOMMENT != 0 && $someoneccd == 1) { 214 | $bug->add_comment("Maintainers CC'd"); 215 | } 216 | } 217 | 218 | # Deal with special requirements: bug 195253 219 | # 1) port is positively identified 220 | # 2) port is in games category 221 | # 3) port is unmaintained 222 | # ==> add games@FreeBSD.org as CC 223 | if (grep { lc($_) eq "games" } @$categories) { 224 | if (grep { lc($_) eq "ports\@freebsd.org" } @$maintainers) { 225 | my $user = get_user("games\@FreeBSD.org", 1); 226 | if ($user) { 227 | $bug->add_cc($user); 228 | } 229 | } 230 | } 231 | 232 | $bug->update(); 233 | 234 | # Switch the user session back. 235 | Bugzilla->set_user($curuser); 236 | } 237 | 238 | sub _get_maintainer { 239 | # we expect _get_maintainer("category/port") 240 | my $port = shift(); 241 | # Make sure category is lower case 242 | # port name can be mixed case 243 | $port =~ s@(.*)/@\L$1/@; 244 | my $portdir = "" . PORTSDIR . "/$port"; 245 | # Does it exist and is a directory? 246 | if (-d $portdir) { 247 | # temporarily manipulate path to allow the exec 248 | # to access all necessary tools 249 | my $oldenv = $ENV{PATH}; 250 | $ENV{PATH} .= "/usr/bin:/usr/local/bin:/usr/local/sbin"; 251 | my $maintainer = `I_DONT_CARE_IF_MY_BUILDS_TARGET_THE_WRONG_RELEASE=yes PORTSDIR=@{[PORTSDIR]} make -C $portdir -V MAINTAINER`; 252 | $ENV{PATH} = $oldenv; 253 | chomp($maintainer); 254 | return $maintainer; 255 | } else { 256 | warn("Port directory $portdir not found"); 257 | } 258 | return; 259 | } 260 | 261 | sub _send_mail_to { 262 | my ($maintainer, $bug) = @_; 263 | my $mail = " 264 | From: bugzilla-noreply\@FreeBSD.org 265 | To: %s 266 | Subject: %s 267 | 268 | Dear FreeBSD port maintainer, 269 | 270 | the following problem report was just filed in the FreeBSD Bugzilla 271 | system, which needs your attention. You can view the report online at 272 | 273 | https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=%d 274 | 275 | The report contents are as follows. 276 | 277 | %s 278 | 279 | "; 280 | my $mailmsg = sprintf( 281 | $mail, 282 | $maintainer, 283 | $bug->short_desc, 284 | $bug->id, 285 | $bug->comments->[0]->body 286 | ); 287 | MessageToMTA($mailmsg, 1); 288 | } 289 | __PACKAGE__->NAME; 290 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::FreeBSDBugUrls; 2 | use strict; 3 | 4 | use constant NAME => 'FreeBSDBugUrls'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::FreeBSDBugUrls; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use base qw(Bugzilla::Extension); 7 | 8 | our $VERSION = '0.1.0'; 9 | 10 | use constant MORE_SUB_CLASSES => qw( 11 | Bugzilla::Extension::FreeBSDBugUrls::BitBucket 12 | Bugzilla::Extension::FreeBSDBugUrls::Phabricator 13 | Bugzilla::Extension::FreeBSDBugUrls::NetBSD 14 | Bugzilla::Extension::FreeBSDBugUrls::GoogleChromium 15 | Bugzilla::Extension::FreeBSDBugUrls::GoogleIssueTracker 16 | Bugzilla::Extension::FreeBSDBugUrls::DragonFlyBSD 17 | Bugzilla::Extension::FreeBSDBugUrls::Gitlabs 18 | ); 19 | 20 | # We need to update bug_see_also table because both 21 | sub bug_url_sub_classes { 22 | my ($self, $args) = @_; 23 | push @{ $args->{sub_classes} }, MORE_SUB_CLASSES; 24 | } 25 | 26 | __PACKAGE__->NAME; 27 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/BitBucket.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::BitBucket; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | # BitBucket issues have the form of 24 | # bitbucket.org/user/project/issue/1234 25 | return (lc($uri->authority) eq "bitbucket.org" 26 | && $uri->path =~ m|[^/]+/[^/]+/issue/\d+|i) ? 1 : 0; 27 | } 28 | 29 | sub _check_value { 30 | my $class = shift; 31 | 32 | my $uri = $class->SUPER::_check_value(@_); 33 | 34 | my ($path) = $uri->path =~ m|([^/]+/[^/]+/issue/\d+)|i; 35 | $uri = new URI("https://bitbucket.org/$path"); 36 | 37 | return $uri; 38 | } 39 | 40 | 1; 41 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/DragonFlyBSD.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::DragonFlyBSD; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | return (lc($uri->authority) eq 'bugs.dragonflybsd.org') ? 1 : 0; 24 | } 25 | 26 | sub _check_value { 27 | my ($class, $uri) = @_; 28 | 29 | $uri = $class->SUPER::_check_value($uri); 30 | 31 | return $uri; 32 | } 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/FreshPorts.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::FreshPorts; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | # FreshPorts issues have the form of 24 | # https://github.com/FreshPorts/freshports/issues/535 25 | # note: historically this has been accepted due to the github.com rule 26 | return (lc($uri->authority) eq "github.com" 27 | && $uri->path =~ m|[^/]+/[^/]+/FreshPorts/freshports/issues/\d+|i) ? 1 : 0; 28 | } 29 | 30 | sub _check_value { 31 | my $class = shift; 32 | 33 | my $uri = $class->SUPER::_check_value(@_); 34 | 35 | my ($path) = $uri->path =~ m|([^/]+/[^/]+/FreshPorts/freshports/issues/\d+)|i; 36 | $uri = new URI("https://github.com/$path"); 37 | 38 | return $uri; 39 | } 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/Gitlabs.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::Gitlabs; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | return 1 if (lc($uri->authority) eq 'gitlab.gnome.org'); 24 | return 1 if (lc($uri->authority) eq 'gitlab.com'); 25 | } 26 | 27 | sub _check_value { 28 | my ($class, $uri) = @_; 29 | 30 | $uri = $class->SUPER::_check_value($uri); 31 | 32 | return $uri; 33 | } 34 | 35 | 1; 36 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/GoogleChromium.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::GoogleChromium; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | # https://bugs.chromium.org/p/PROJECT/issues/detail?id=8359 24 | return (lc($uri->authority) eq "bugs.chromium.org" 25 | && ($uri->path =~ m|^/p/[^/]+/issues/detail$|i 26 | and $uri->query_param('id') =~ m|^\d+$| 27 | )) ? 1 : 0; 28 | } 29 | 30 | sub _check_value { 31 | my $class = shift; 32 | 33 | my $uri = $class->SUPER::_check_value(@_); 34 | 35 | return $uri; 36 | } 37 | 38 | 1; 39 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/GoogleIssueTracker.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::GoogleIssueTracker; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | # Google Issue Tracker 24 | # http(s)://issuetracker.google.com/issues/ID 25 | return (lc($uri->authority) eq 'issuetracker.google.com' 26 | and $uri->path =~ m|^/issues/\d+$|) ? 1 : 0; 27 | } 28 | 29 | sub _check_value { 30 | my ($class, $uri) = @_; 31 | 32 | $uri = $class->SUPER::_check_value($uri); 33 | 34 | $uri->scheme('https'); 35 | 36 | return $uri; 37 | } 38 | 39 | 1; 40 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/NetBSD.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::NetBSD; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | return (lc($uri->authority) eq 'gnats.netbsd.org') ? 1 : 0; 24 | } 25 | 26 | sub _check_value { 27 | my ($class, $uri) = @_; 28 | 29 | $uri = $class->SUPER::_check_value($uri); 30 | 31 | return $uri; 32 | } 33 | 34 | 1; 35 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/Phabricator.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::Phabricator; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | return (lc($uri->authority) eq 'reviews.freebsd.org' 24 | and $uri->path =~ m!^/D\d+$!) ? 1 : 0; 25 | } 26 | 27 | sub _check_value { 28 | my ($class, $uri) = @_; 29 | 30 | $uri = $class->SUPER::_check_value($uri); 31 | 32 | # Phabricator HTTP URLs redirect to HTTPS, so just use the HTTPS scheme. 33 | $uri->scheme('https'); 34 | 35 | return $uri; 36 | } 37 | 38 | 1; 39 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/lib/Sourceforge.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::FreeBSDBugUrls::Sourceforge; 9 | 10 | use 5.10.1; 11 | use strict; 12 | use warnings; 13 | 14 | use parent qw(Bugzilla::BugUrl); 15 | 16 | ############################### 17 | #### Methods #### 18 | ############################### 19 | 20 | sub should_handle { 21 | my ($class, $uri) = @_; 22 | 23 | # Sourceforge issues have the form of 24 | # https://sourceforge.net/p//bugs// 25 | # In theory, Bugzilla claims that it handles these natively. 26 | return (lc($uri->authority) eq "sourceforge.net" 27 | && $uri->path =~ m|[^/]+/[^/]+/bugs/\d+|i) ? 1 : 0; 28 | } 29 | 30 | sub _check_value { 31 | my $class = shift; 32 | 33 | my $uri = $class->SUPER::_check_value(@_); 34 | 35 | my ($path) = $uri->path =~ m|([^/]+/[^/]+/bugs/\d+)|i; 36 | $uri = new URI("https://sourceforge.net/$path"); 37 | 38 | return $uri; 39 | } 40 | 41 | 1; 42 | -------------------------------------------------------------------------------- /extensions/FreeBSDBugUrls/template/en/default/hook/global/user-error-bug_url_invalid_tracker.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 |
  • An issue on bitbucket.org.
  • 10 |
  • A PR on the DragonFly bug tracker.
  • 11 |
  • An issue on Gitlabs.
  • 12 |
  • An issue on the Google Chromium issue tracker.
  • 13 |
  • An issue on the Google issue tracker.
  • 14 |
  • A PR on the NetBSD GNATS system.
  • 15 |
  • A review on reviews.FreeBSD.org (Phabricator).
  • 16 | -------------------------------------------------------------------------------- /extensions/HideComponents/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::HideComponents; 2 | use strict; 3 | 4 | use constant NAME => 'HideComponents'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/HideComponents/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::HideComponents; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use base qw(Bugzilla::Extension); 7 | 8 | our $VERSION = '0.1.0'; 9 | 10 | 11 | sub template_before_process { 12 | my ($self, $args) = @_; 13 | my ($vars, $file) = @$args{qw(vars file)}; 14 | 15 | return if $file ne 'bug/create/create.html.tmpl'; 16 | my $user = Bugzilla->user; 17 | # Limit noise from mis-classified PRs by non-committer 18 | # https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=198411 19 | if (!$user->in_group('freebsd_committer')) { 20 | $vars->{hide_components} = [ 21 | 'Package Infrastructure', 22 | 'Ports Framework' 23 | ]; 24 | } 25 | } 26 | 27 | __PACKAGE__->NAME; 28 | -------------------------------------------------------------------------------- /extensions/HideSPAM/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::HideSPAM; 2 | use strict; 3 | 4 | use constant NAME => 'HideSPAM'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/HideSPAM/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::HideSPAM; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use base qw(Bugzilla::Extension); 7 | 8 | our $VERSION = '0.1.0'; 9 | 10 | sub bug_format_comment { 11 | my ($self, $args) = @_; 12 | my $regexes = $args->{regexes}; 13 | my $bug = $args->{bug}; 14 | my $comment = $args->{comment}; 15 | 16 | my $user; 17 | $user = $comment->author if (defined($comment)); 18 | $user = $bug->reporter if (defined($bug) && !defined($user)); 19 | if (defined($user) && ($user->disabledtext =~ /\[spam\]/i)) { 20 | push(@$regexes, { 21 | match => qr/.+/is, 22 | replace => "MARKED AS SPAM", 23 | }); 24 | } 25 | } 26 | 27 | __PACKAGE__->NAME; 28 | -------------------------------------------------------------------------------- /extensions/InlineHistory/Config.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::InlineHistory; 9 | use strict; 10 | 11 | use constant NAME => 'InlineHistory'; 12 | 13 | __PACKAGE__->NAME; 14 | -------------------------------------------------------------------------------- /extensions/InlineHistory/Extension.pm: -------------------------------------------------------------------------------- 1 | # This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | 8 | package Bugzilla::Extension::InlineHistory; 9 | use strict; 10 | use base qw(Bugzilla::Extension); 11 | 12 | use Bugzilla::User::Setting; 13 | use Bugzilla::Constants; 14 | use Bugzilla::Attachment; 15 | 16 | our $VERSION = '1.5'; 17 | 18 | # don't show inline history for bugs with lots of changes 19 | use constant MAXIMUM_ACTIVITY_COUNT => 500; 20 | 21 | # don't show really long values 22 | use constant MAXIMUM_VALUE_LENGTH => 256; 23 | 24 | sub template_before_create { 25 | my ($self, $args) = @_; 26 | $args->{config}->{FILTERS}->{ih_short_value} = sub { 27 | my ($str) = @_; 28 | return length($str) <= MAXIMUM_VALUE_LENGTH 29 | ? $str 30 | : substr($str, 0, MAXIMUM_VALUE_LENGTH - 3) . '...'; 31 | }; 32 | } 33 | 34 | sub template_before_process { 35 | my ($self, $args) = @_; 36 | my $file = $args->{'file'}; 37 | my $vars = $args->{'vars'}; 38 | 39 | return if $file ne 'bug/edit.html.tmpl'; 40 | 41 | my $user = Bugzilla->user; 42 | my $dbh = Bugzilla->dbh; 43 | return unless $user->id && $user->settings->{'inline_history'}->{'value'} eq 'on'; 44 | 45 | # note: bug/edit.html.tmpl doesn't support multiple bugs 46 | my $bug = exists $vars->{'bugs'} ? $vars->{'bugs'}[0] : $vars->{'bug'}; 47 | my $bug_id = $bug->id; 48 | 49 | # build bug activity 50 | my ($activity) = $bug->can('get_activity') 51 | ? $bug->get_activity() 52 | : Bugzilla::Bug::GetBugActivity($bug_id); 53 | $activity = _add_duplicates($bug_id, $activity); 54 | 55 | if (scalar @$activity > MAXIMUM_ACTIVITY_COUNT) { 56 | $activity = []; 57 | $vars->{'ih_activity'} = 0; 58 | $vars->{'ih_activity_max'} = 1; 59 | return; 60 | } 61 | 62 | # allow other extensions to alter history 63 | Bugzilla::Hook::process('inline_history_activtiy', { activity => $activity }); 64 | 65 | my %attachment_cache; 66 | foreach my $attachment (@{$bug->attachments}) { 67 | $attachment_cache{$attachment->id} = $attachment; 68 | } 69 | 70 | # build a list of bugs we need to check visibility of, so we can check with a single query 71 | my %visible_bug_ids; 72 | 73 | # augment and tweak 74 | foreach my $operation (@$activity) { 75 | # make operation.who an object 76 | $operation->{who} = 77 | Bugzilla::User->new({ name => $operation->{who}, cache => 1 }); 78 | 79 | for (my $i = 0; $i < scalar(@{$operation->{changes}}); $i++) { 80 | my $change = $operation->{changes}->[$i]; 81 | 82 | # make an attachment object 83 | if ($change->{attachid}) { 84 | $change->{attach} = $attachment_cache{$change->{attachid}}; 85 | } 86 | 87 | # empty resolutions are displayed as --- by default 88 | # make it explicit here to enable correct display of the change 89 | if ($change->{fieldname} eq 'resolution') { 90 | $change->{removed} = '---' if $change->{removed} eq ''; 91 | $change->{added} = '---' if $change->{added} eq ''; 92 | } 93 | 94 | # make boolean fields true/false instead of 1/0 95 | my ($table, $field) = ('bugs', $change->{fieldname}); 96 | if ($field =~ /^([^\.]+)\.(.+)$/) { 97 | ($table, $field) = ($1, $2); 98 | } 99 | my $column = $dbh->bz_column_info($table, $field); 100 | if ($column && $column->{TYPE} eq 'BOOLEAN') { 101 | $change->{removed} = ''; 102 | $change->{added} = $change->{added} ? 'true' : 'false'; 103 | } 104 | 105 | my $field_obj; 106 | if ($change->{fieldname} =~ /^cf_/) { 107 | $field_obj = Bugzilla::Field->new({ name => $change->{fieldname}, cache => 1 }); 108 | } 109 | 110 | # identify buglist changes 111 | if ($change->{fieldname} eq 'blocked' || 112 | $change->{fieldname} eq 'dependson' || 113 | $change->{fieldname} eq 'dupe' || 114 | ($field_obj && $field_obj->type == FIELD_TYPE_BUG_ID) 115 | ) { 116 | $change->{buglist} = 1; 117 | foreach my $what (qw(removed added)) { 118 | my @buglist = split(/[\s,]+/, $change->{$what}); 119 | foreach my $id (@buglist) { 120 | if ($id && $id =~ /^\d+$/) { 121 | $visible_bug_ids{$id} = 1; 122 | } 123 | } 124 | } 125 | } 126 | 127 | # split multiple flag changes (must be processed last) 128 | if ($change->{fieldname} eq 'flagtypes.name') { 129 | my @added = split(/, /, $change->{added}); 130 | my @removed = split(/, /, $change->{removed}); 131 | next if scalar(@added) <= 1 && scalar(@removed) <= 1; 132 | # remove current change 133 | splice(@{$operation->{changes}}, $i, 1); 134 | # restructure into added/removed for each flag 135 | my %flags; 136 | foreach my $added (@added) { 137 | my ($value, $name) = $added =~ /^((.+).)$/; 138 | $flags{$name}{added} = $value; 139 | $flags{$name}{removed} |= ''; 140 | } 141 | foreach my $removed (@removed) { 142 | my ($value, $name) = $removed =~ /^((.+).)$/; 143 | $flags{$name}{added} |= ''; 144 | $flags{$name}{removed} = $value; 145 | } 146 | # clone current change, modify and insert 147 | foreach my $flag (sort keys %flags) { 148 | my $flag_change = {}; 149 | foreach my $key (keys %$change) { 150 | $flag_change->{$key} = $change->{$key}; 151 | } 152 | $flag_change->{removed} = $flags{$flag}{removed}; 153 | $flag_change->{added} = $flags{$flag}{added}; 154 | splice(@{$operation->{changes}}, $i, 0, $flag_change); 155 | } 156 | $i--; 157 | } 158 | } 159 | } 160 | 161 | $user->visible_bugs([keys %visible_bug_ids]); 162 | 163 | $vars->{'ih_activity'} = $activity; 164 | } 165 | 166 | sub _add_duplicates { 167 | # insert 'is a dupe of this bug' comment to allow js to display 168 | # as activity 169 | 170 | my ($bug_id, $activity) = @_; 171 | 172 | # we're ignoring pre-bugzilla 3.0 ".. has been marked as a duplicate .." 173 | # comments because searching each comment's text is expensive. these 174 | # legacy comments will not be visible at all in the bug's comment/activity 175 | # stream. bug 928786 deals with migrating those comments to be stored as 176 | # CMT_HAS_DUPE instead. 177 | 178 | my $dbh = Bugzilla->dbh; 179 | my $sth = $dbh->prepare(" 180 | SELECT profiles.login_name, " . 181 | $dbh->sql_date_format('bug_when', '%Y.%m.%d %H:%i:%s') . ", 182 | extra_data 183 | FROM longdescs 184 | INNER JOIN profiles ON profiles.userid = longdescs.who 185 | WHERE bug_id = ? AND type = ? 186 | ORDER BY bug_when 187 | "); 188 | $sth->execute($bug_id, CMT_HAS_DUPE); 189 | 190 | while (my($who, $when, $dupe_id) = $sth->fetchrow_array) { 191 | my $entry = { 192 | 'when' => $when, 193 | 'who' => $who, 194 | 'changes' => [ 195 | { 196 | 'removed' => '', 197 | 'added' => $dupe_id, 198 | 'attachid' => undef, 199 | 'fieldname' => 'dupe', 200 | 'dupe' => 1, 201 | } 202 | ], 203 | }; 204 | push @$activity, $entry; 205 | } 206 | 207 | return [ sort { $a->{when} cmp $b->{when} } @$activity ]; 208 | } 209 | 210 | sub install_before_final_checks { 211 | my ($self, $args) = @_; 212 | add_setting('inline_history', ['on', 'off'], 'off'); 213 | } 214 | 215 | __PACKAGE__->NAME; 216 | -------------------------------------------------------------------------------- /extensions/InlineHistory/README: -------------------------------------------------------------------------------- 1 | InlineHistory inserts bug activity inline with the comments when viewing a bug. 2 | It was derived from the Bugzilla Tweaks Addon by Ehasn Akhgari. 3 | 4 | For technical and performance reasons it is only available to logged in users, 5 | and is enabled by a User Preference. 6 | 7 | It works with an unmodified install of Bugzilla 4.0 and 4.2. 8 | 9 | If you have modified your show_bug template, the javascript in 10 | web/inline-history.js may need to be updated to suit your installation. 11 | -------------------------------------------------------------------------------- /extensions/InlineHistory/template/en/default/hook/bug/comments-aftercomments.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [% RETURN UNLESS ih_activity %] 10 | [%# this div exists to allow bugzilla-tweaks to detect when we're active %] 11 |
    12 | 13 | 130 | 131 | [% BLOCK add_change %] 132 | html += '[%~%] 133 | [% IF change.fieldname == 'estimated_time' || 134 | change.fieldname == 'remaining_time' || 135 | change.fieldname == 'work_time' %] 136 | [% PROCESS formattimeunit time_unit = value FILTER html FILTER js %] 137 | [% ELSIF change.buglist %] 138 | [% value FILTER bug_list_link FILTER js %] 139 | [% ELSIF change.fieldname == 'bug_file_loc' %] 140 | [%~%] 143 | [%~%][% value FILTER ih_short_value FILTER html FILTER js %] 144 | [% ELSIF change.fieldname == 'see_also' %] 145 | [% FOREACH see_also = value.split(', ') %] 146 | [%~%] 147 | [%~%][% see_also FILTER html FILTER js %] 148 | [%- ", " IF NOT loop.last %] 149 | [% END %] 150 | [% ELSIF change.fieldname == 'assigned_to' || 151 | change.fieldname == 'reporter' || 152 | change.fieldname == 'qa_contact' || 153 | change.fieldname == 'cc' || 154 | change.fieldname == 'flagtypes.name' %] 155 | [% value FILTER email FILTER js %] 156 | [% ELSE %] 157 | [% value FILTER ih_short_value FILTER html FILTER js %] 158 | [% END %] 159 | [%~ %]'; 160 | [% END %] 161 | -------------------------------------------------------------------------------- /extensions/InlineHistory/template/en/default/hook/bug/comments-comment_banner.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [% IF ih_activity_max %] 10 |

    11 | This [% terms.bug %] contains too many changes to be displayed inline. 12 |

    13 | [% END %] 14 | -------------------------------------------------------------------------------- /extensions/InlineHistory/template/en/default/hook/bug/show-header-end.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [% IF user.id && user.settings.inline_history.value == "on" %] 10 | [% style_urls.push('extensions/InlineHistory/web/style.css') %] 11 | [% javascript_urls.push('extensions/InlineHistory/web/inline-history.js') %] 12 | [% END %] 13 | -------------------------------------------------------------------------------- /extensions/InlineHistory/template/en/default/hook/global/setting-descs-settings.none.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [% 10 | setting_descs.inline_history = "When viewing a $terms.bug, show all $terms.bug activity", 11 | %] 12 | -------------------------------------------------------------------------------- /extensions/InlineHistory/web/style.css: -------------------------------------------------------------------------------- 1 | /* This Source Code Form is subject to the terms of the Mozilla Public 2 | * License, v. 2.0. If a copy of the MPL was not distributed with this file, 3 | * You can obtain one at http://mozilla.org/MPL/2.0/. 4 | * 5 | * This Source Code Form is "Incompatible With Secondary Licenses", as 6 | * defined by the Mozilla Public License, v. 2.0. */ 7 | 8 | .ih_history, .ih_inlinehistory { 9 | font-size: 85%; 10 | border-top: 1px solid #C8C8BA; 11 | border-bottom: 1px solid #C8C8BA; 12 | border-radius: 0.25em; 13 | background-color: #EEE !important; 14 | padding: 10px !important; 15 | color: #444; 16 | } 17 | 18 | .bz_comment.ih_history { 19 | padding: 5px 5px 0px 5px 20 | } 21 | 22 | .ih_history_item { 23 | margin-bottom: 5px; 24 | } 25 | 26 | .ih_hidden { 27 | display: none; 28 | } 29 | 30 | .ih_deleted { 31 | text-decoration: line-through; 32 | } 33 | -------------------------------------------------------------------------------- /extensions/Reporting/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Reporting; 2 | use strict; 3 | 4 | use constant NAME => 'Reporting'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/Reporting/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Reporting; 2 | use strict; 3 | use base qw(Bugzilla::Extension); 4 | 5 | use Bugzilla; 6 | use Bugzilla::Constants; 7 | use Bugzilla::Error; 8 | use Bugzilla::Util; 9 | use Bugzilla::Extension::Reporting::Graphs; 10 | 11 | our $VERSION = '0.1.0'; 12 | 13 | sub get_products { 14 | my $remove = shift(); 15 | my $products = Bugzilla->user->get_selectable_products(); 16 | my @retval; 17 | if (defined($remove)) { 18 | my %rm = map { $_ => 1 } @$remove; 19 | foreach my $p (@$products) { 20 | next if exists($rm{$p->name}); 21 | push(@retval, $p->name); 22 | } 23 | } else { 24 | @retval = map($_->name, @$products); 25 | } 26 | return \@retval; 27 | } 28 | 29 | sub get_reports { 30 | return [ 31 | { 32 | title => "Total number of bugs", 33 | type => "total_bugs_over_time", 34 | desc => "Total number of bugs over time", 35 | func => \&total_bugs_over_time, 36 | args => { 37 | file => "total_bugs_over_time" 38 | } 39 | }, 40 | { 41 | title => "Number of bugs per product", 42 | type => "total_bugs_per_product_over_time", 43 | desc => "Total number of bugs per product over time", 44 | func => \&total_bugs_per_product_over_time, 45 | args => { 46 | file => "total_bugs_per_product_over_time", 47 | products => get_products(), 48 | }, 49 | }, 50 | { 51 | title => "Number of bugs per product without Ports", 52 | type => "total_bugs_per_product_over_time_no_ports", 53 | desc => "Total number of bugs per product over time without Ports", 54 | func => \&total_bugs_per_product_over_time, 55 | args => { 56 | file => "total_bugs_per_product_over_time_no_ports", 57 | products => get_products(["Ports & Packages"]), 58 | }, 59 | }, 60 | { 61 | title => "Total open number of bugs", 62 | type => "total_open_bugs_over_time", 63 | desc => "Total open number of bugs over time", 64 | func => \&total_open_bugs_over_time, 65 | args => { 66 | file => "total_open_bugs_over_time" 67 | } 68 | }, 69 | # TODO: not implemented yet 70 | # { 71 | # title => "Total open number of bugs per product", 72 | # type => "total_open_bugs_per_product_over_time", 73 | # desc => "Total open number of bugs over time per product", 74 | # func => \&total_open_bugs_per_product_over_time, 75 | # args => { 76 | # file => "total_open_bugs_per_product_over_time" 77 | # } 78 | # }, 79 | ]; 80 | } 81 | # Bars: 82 | 83 | # Number of PRs by category 84 | # Number of PRs by category without ports 85 | 86 | # Number of PRs by category and status 87 | # Number of PRs by category and statuswithout ports 88 | 89 | sub page_before_template { 90 | my ($self, $args) = @_; 91 | my $page = $args->{page_id}; 92 | my $vars = $args->{vars}; 93 | 94 | if ($page ne "reporting.html" && $page ne "showreport.html") { 95 | # Do not do anything, if the currently requested page is not 96 | # the report page. 97 | return; 98 | } 99 | if ($page eq "showreport.html") { 100 | show_report($page, $args); 101 | } elsif ($page eq "reporting.html") { 102 | show_overview($page, $args); 103 | } 104 | } 105 | 106 | sub show_overview { 107 | my ($page, $args) = @_; 108 | my $vars = $args->{vars}; 109 | $vars->{reports} = get_reports(); 110 | } 111 | 112 | sub show_report { 113 | my ($page, $args) = @_; 114 | my $vars = $args->{vars}; 115 | my $cgi = Bugzilla->cgi; 116 | 117 | # Ensure that the GET parameters are valid 118 | if (!defined($cgi->param("type"))) { 119 | ThrowUserError("invalid_report_type"); 120 | } 121 | my $type = $cgi->param("type"); 122 | 123 | my @reports = grep($_->{type} eq $type, @{ get_reports() }); 124 | if (scalar(@reports) != 1) { 125 | ThrowUserError("invalid_report_type"); 126 | } 127 | my $report = $reports[0]; 128 | 129 | my @images; 130 | $vars->{report} = $report; 131 | 132 | push(@images, { title => $report->{title}, 133 | url => $report->{func}($report->{args}) }); 134 | $vars->{images} = \@images; 135 | } 136 | 137 | __PACKAGE__->NAME; 138 | -------------------------------------------------------------------------------- /extensions/Reporting/lib/Charting.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Reporting::Charting; 2 | 3 | use strict; 4 | use Bugzilla; 5 | use Bugzilla::Constants; 6 | 7 | use Chart::Bars; 8 | use Chart::Lines; 9 | use File::Basename; 10 | use POSIX qw(strftime); 11 | 12 | use base qw(Exporter); 13 | our @EXPORT = qw( 14 | get_chart_file chart_lines chart_bars 15 | ); 16 | 17 | use constant COLORMAP => { 18 | "dataset0" => [173, 35, 35], 19 | "dataset1" => [42, 75, 215], 20 | "dataset2" => [129, 38, 192], 21 | "dataset3" => [41, 208, 208], 22 | "dataset4" => [255, 146, 51], 23 | "dataset5" => [29, 105, 20], 24 | "dataset6" => [233, 222, 187], 25 | "dataset7" => [157, 175, 255], 26 | "dataset8" => [129, 74, 25], 27 | "dataset9" => [0,0,0] 28 | }; 29 | 30 | use constant LINE_SETTINGS => ( 31 | "grey_background" => "false", 32 | "y_grid_lines" => "true", 33 | "x_grid_lines" => "false", 34 | "brush_size" => 4, 35 | "x_ticks" => "vertical", 36 | "precision" => 0 37 | ); 38 | 39 | use constant BAR_SETTINGS => ( 40 | "grey_background" => "false", 41 | "y_grid_lines" => "true", 42 | "x_grid_lines" => "true", 43 | "brush_size" => 4, 44 | "x_ticks" => "vertical", 45 | "precision" => 0 46 | ); 47 | 48 | use constant { 49 | WIDTH => 1000, 50 | HEIGHT => 700, 51 | TICKS => 20, 52 | }; 53 | 54 | sub get_chart_file { 55 | my $filename = shift(); 56 | my $date = strftime('%Y-%m-%d', localtime()); 57 | my $graphdir = bz_locations()->{"graphsdir"}; 58 | my $imgfile = join("/", $graphdir, "$filename-$date.png"); 59 | 60 | my $graphurl = basename($graphdir); 61 | my $imgurl = join("/", $graphurl, "$filename-$date.png"); 62 | 63 | my $recreate = 1; 64 | if (-e $imgfile) { 65 | # For debugging purposes: change to 1 66 | $recreate = 0; 67 | } 68 | return ($imgfile, $imgurl, $recreate); 69 | } 70 | 71 | # Expects the following arguments: 72 | # - a filename to store the graph to. 73 | # - an arrayref containing the x-axis labels: (label1, label2, ...) 74 | # - a hashref containing the datasets: 75 | # 76 | # { "datasetname 1" => (value1, value2, ....), 77 | # "datasetname 1" => (value1, value2, ....), ... } 78 | # 79 | # the values of the hashref must be lists matching the size of the 80 | # x-axis labels 81 | # - a hash containing the settings for Chart::Lines (see its 82 | # chart->set() documentation) 83 | sub chart_lines { 84 | my ($filename, $xlabels, $datasets, %settings) = @_; 85 | 86 | my $chart = Chart::Lines->new(WIDTH, HEIGHT); 87 | $chart->set("colors", COLORMAP); 88 | my %s = (%settings, LINE_SETTINGS); 89 | $chart->set(%s); 90 | my $num = 0; 91 | 92 | # Calculate the resolution 93 | if (scalar(@$xlabels) > TICKS) { 94 | my $skipx = int((scalar(@$xlabels) + TICKS - 1) / TICKS); 95 | $chart->set("skip_x_ticks" => $skipx); 96 | } 97 | my @dslabels; 98 | my @ds; 99 | foreach my $k (keys %$datasets) { 100 | push(@dslabels, $k); 101 | push(@ds, $datasets->{$k}); 102 | } 103 | $chart->set("legend_labels", \@dslabels); 104 | my @mref = ($xlabels, @ds); 105 | $chart->png($filename, \@mref); 106 | } 107 | 108 | # Expects the following arguments: 109 | # - a filename to store the graph to. 110 | # - an arrayref containing the x-axis labels: (label1, label2, ...) 111 | # - a hashref containing the datasets: 112 | # 113 | # { "datasetname 1" => (value1, value2, ....), 114 | # "datasetname 1" => (value1, value2, ....), ... } 115 | # 116 | # the values of the hashref must be lists matching the size of the 117 | # x-axis labels 118 | # - a hash containing the settings for Chart::Bars (see its 119 | # chart->set() documentation) 120 | sub chart_bars { 121 | my ($filename, $xlabels, $datasets, %settings) = @_; 122 | 123 | my $chart = Chart::Bars->new(WIDTH, HEIGHT); 124 | $chart->set(%settings); 125 | $chart->set("colors", COLORMAP); 126 | my $num = 0; 127 | 128 | my @legends; 129 | my @data; 130 | foreach my $k (keys %$datasets) { 131 | push(@legends, $k); 132 | push(@data, $datasets->{$k}); 133 | } 134 | $chart->set("legend_labels", \@legends); 135 | my @mref = ($xlabels, @data); 136 | $chart->png($filename, \@mref); 137 | } 138 | 139 | 1; 140 | -------------------------------------------------------------------------------- /extensions/Reporting/lib/Graphs.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Reporting::Graphs; 2 | 3 | use strict; 4 | use Bugzilla; 5 | use Bugzilla::Extension::Reporting::Charting; 6 | use Bugzilla::Extension::Reporting::Queries; 7 | 8 | use base qw(Exporter); 9 | our @EXPORT = qw( 10 | total_bugs_over_time total_bugs_per_product_over_time 11 | total_open_bugs_over_time total_open_bugs_per_product_over_time 12 | total_open_bugs_per_product_over_time 13 | ); 14 | 15 | sub total_bugs_over_time { 16 | my $args = shift(); 17 | 18 | my %settings = ( 19 | "title" => "Total number of bugs", 20 | "x_label" => "Date", 21 | "y_label" => "Count", 22 | ); 23 | 24 | my ($labels, $datasets) = bugs_over_time(); 25 | my ($name, $url, $create) = get_chart_file($args->{file}); 26 | 27 | if ($create != 0) { 28 | chart_lines($name, $labels, $datasets, %settings); 29 | } 30 | return $url; 31 | } 32 | 33 | sub total_bugs_per_product_over_time { 34 | my $args = shift(); 35 | 36 | my %settings = ( 37 | "title" => "Total number of bugs per product", 38 | "x_label" => "Date", 39 | "y_label" => "Count", 40 | ); 41 | 42 | my ($labels, $datasets) = bugs_over_time_per_product(); 43 | my %products = map { $_ => 1 } @{ $args->{products} }; 44 | 45 | # Delete all products not being requested. 46 | foreach my $key (keys %$datasets) { 47 | next if exists($products{$key}); 48 | delete $datasets->{$key}; 49 | } 50 | 51 | my ($name, $url, $create) = get_chart_file($args->{file}); 52 | if ($create != 0) { 53 | chart_lines($name, $labels, $datasets, %settings); 54 | } 55 | return $url; 56 | } 57 | 58 | sub total_open_bugs_over_time { 59 | my $args = shift(); 60 | 61 | my %settings = ( 62 | "title" => "Total open number of bugs", 63 | "x_label" => "Date", 64 | "y_label" => "Count", 65 | ); 66 | my ($labels, $datasets) = bugs_over_time_per_status(); 67 | delete $datasets->{"Closed"}; 68 | 69 | # Melt the datasets - we jut want to have a single one. 70 | my @data; 71 | my $ds = { "Open Bugs" => \@data }; 72 | my $cnt = scalar(@$labels) - 1; 73 | for my $i ( 0..$cnt ) { 74 | my $c = 0; 75 | foreach my $k (keys %$datasets) { 76 | $c += $datasets->{$k}->[$i]; 77 | } 78 | push(@data, $c); 79 | } 80 | my ($name, $url, $create) = get_chart_file($args->{file}); 81 | if ($create != 0) { 82 | chart_lines($name, $labels, $ds, %settings); 83 | } 84 | return $url; 85 | } 86 | 87 | sub total_open_bugs_per_product_over_time { 88 | # TODO 89 | } 90 | -------------------------------------------------------------------------------- /extensions/Reporting/lib/Queries.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::Reporting::Queries; 2 | 3 | use strict; 4 | use Bugzilla; 5 | use base qw(Exporter); 6 | our @EXPORT = qw( 7 | bugs_over_time bugs_over_time_per_product bugs_per_product 8 | bugs_per_status_per_product bugs_over_time_per_status 9 | ); 10 | 11 | sub bugs_over_time { 12 | my $dbh = Bugzilla->dbh; 13 | my $query = q{ 14 | SELECT bugs.creation_ts::date AS bdate, 15 | count(bugs.bug_id) AS amount 16 | FROM bugs 17 | GROUP BY bdate; 18 | }; 19 | my $bugs = $dbh->selectall_arrayref($query, undef); 20 | # The raw data will be 21 | # bugs = [(date, amount), (date, amount), ...] 22 | # Transform it into something better as return value: 23 | # 24 | # ( (date, date, date ....), 25 | # { bugs => (amount, amount, amount, ...) } 26 | # ) 27 | my @xlabels = map($_->[0], @$bugs); 28 | my $d = 0; 29 | my @amounts; 30 | foreach my $e (@$bugs) { 31 | $d += $e->[1]; 32 | push(@amounts, $d); 33 | } 34 | my $dataset = { "Bugs" => \@amounts }; 35 | return (\@xlabels, $dataset); 36 | } 37 | 38 | sub bugs_over_time_per_product { 39 | my $dbh = Bugzilla->dbh; 40 | my @products = @{ $dbh->selectall_arrayref(qq{ 41 | SELECT name FROM products; 42 | }, { Slice => {} }) }; 43 | my $query = q{ 44 | SELECT bugs.creation_ts::date AS bdate, 45 | products.name AS product, 46 | count(bugs.bug_id) AS amount 47 | FROM bugs JOIN products ON (products.id = bugs.product_id) 48 | GROUP BY bdate, products.name; 49 | }; 50 | my $bugs = $dbh->selectall_arrayref($query, undef); 51 | # The raw data will be 52 | # bugs = [(date, product, amount), (date, product, amount), ...] 53 | # Transform it into something better as return value: 54 | # 55 | # ( (date, date, date ....), 56 | # { product => (amount, amount, amount, ...), 57 | # product => (amount, ....), 58 | # } ) 59 | # 60 | my @xlabels; 61 | my $datasets = {}; 62 | my $curvals = {}; 63 | foreach my $p (@products) { 64 | my @d = (); 65 | $datasets->{$p->{name}} = \@d; 66 | $curvals->{$p->{name}} = 0; 67 | } 68 | my $offs = 0; 69 | my $curd = ""; 70 | foreach my $rec (@$bugs) { 71 | my ($d, $p, $a) = @$rec; 72 | if ($curd ne $d) { 73 | foreach my $key (keys %$datasets) { 74 | my $data = $datasets->{$key}; 75 | while (scalar(@$data) < $offs) { 76 | push(@$data, $curvals->{$key}); 77 | } 78 | } 79 | $curd = $d; 80 | push(@xlabels, $d); 81 | $offs += 1; 82 | } 83 | my $dataset = $datasets->{$p}; 84 | $curvals->{$p} += $a; 85 | push(@$dataset, $curvals->{$p}); 86 | } 87 | return (\@xlabels, $datasets); 88 | } 89 | 90 | sub bugs_per_product { 91 | my $dbh = Bugzilla->dbh; 92 | my $query = q{ 93 | SELECT count(bug_id) AS amount, products.name AS product 94 | FROM bugs JOIN products ON (products.id = bugs.product_id) 95 | GROUP BY products.name; 96 | }; 97 | my $bugs = $dbh->selectall_arrayref($query, undef); 98 | # The raw data will be 99 | # bugs = [(product, amount), (product, amount), ...] 100 | return $bugs; 101 | } 102 | 103 | sub bugs_per_status_per_product { 104 | my $dbh = Bugzilla->dbh; 105 | my @products = @{ $dbh->selectall_arrayref(qq{ 106 | SELECT name FROM products; 107 | }, { Slice => {} }) }; 108 | my $query = q{ 109 | SELECT count(bug_id) AS amount, 110 | bug_status AS status, 111 | products.name AS product 112 | FROM bugs JOIN products ON (products.id = bugs.product_id) 113 | GROUP BY bug_status, products.name 114 | ORDER BY products.name, bug_status; 115 | }; 116 | my $bugs = $dbh->selectall_arrayref($query, undef); 117 | # The raw data will be 118 | # bugs = [(amount, status, product), (amount, status, product), ...] 119 | # The tranformed data will be: 120 | # 121 | # ( (status, status, status, ...), 122 | # { product => (val1, val2, val3), 123 | # product => (val1, ...) } 124 | # ) 125 | # 126 | my $datasets = {}; 127 | foreach my $p (@products) { 128 | my @d = map($_->[0], grep($_->[2] eq $p->{name}, @$bugs)); 129 | $datasets->{$p->{name}} = \@d; 130 | } 131 | my $prod = $bugs->[0]->[2]; 132 | my @xlabels = map($_->[1], grep($_->[2] eq $prod, @$bugs)); 133 | return (\@xlabels, $datasets); 134 | } 135 | 136 | sub bugs_over_time_per_status { 137 | my $dbh = Bugzilla->dbh; 138 | my @status = @{ $dbh->selectall_arrayref(qq{ 139 | SELECT value FROM bug_status; 140 | }, { Slice => {} }) }; 141 | my $query = q{ 142 | SELECT bdate, added, removed, count(bdate) AS amount 143 | FROM ( 144 | SELECT bugs.creation_ts::date AS bdate, 'New'::varchar(255) AS added, NULL as removed 145 | FROM bugs 146 | UNION ALL 147 | SELECT bug_when::date as bdate, added, removed 148 | FROM bugs_activity 149 | WHERE fieldid = (SELECT id FROM fielddefs WHERE name = 'bug_status') 150 | ) AS data 151 | GROUP BY bdate, added, removed 152 | ORDER BY bdate, added, removed; 153 | }; 154 | my $bugs = $dbh->selectall_arrayref($query, undef); 155 | my @xlabels; 156 | my $datasets = {}; 157 | my $curvals = {}; 158 | foreach my $p (@status) { 159 | my @d = (); 160 | $datasets->{$p->{value}} = \@d; 161 | $curvals->{$p->{value}} = 0; 162 | } 163 | my $offs = 0; 164 | my $curd = ""; 165 | foreach my $rec (@$bugs) { 166 | my ($d, $a, $r, $t) = @$rec; 167 | if ($curd ne $d) { 168 | foreach my $key (keys %$datasets) { 169 | my $data = $datasets->{$key}; 170 | while (scalar(@$data) < $offs) { 171 | push(@$data, $curvals->{$key}); 172 | } 173 | } 174 | $curd = $d; 175 | push(@xlabels, $d); 176 | $offs += 1; 177 | } 178 | $curvals->{$a} += $t; 179 | if (defined($r) && $r ne "") { 180 | $curvals->{$r} -= $t; 181 | } 182 | } 183 | # Store the last set 184 | foreach my $key (keys %$datasets) { 185 | my $data = $datasets->{$key}; 186 | push(@$data, $curvals->{$key}); 187 | } 188 | return (\@xlabels, $datasets); 189 | } 190 | 191 | sub bugs_over_time_per_product_per_status { 192 | my $dbh = Bugzilla->dbh; 193 | my @status = @{ $dbh->selectall_arrayref(qq{ 194 | SELECT value FROM bug_status; 195 | }, { Slice => {} }) }; 196 | my @products = @{ $dbh->selectall_arrayref(qq{ 197 | SELECT id, name FROM products ORDER BY id; 198 | }, undef)}; 199 | my $query = q{ 200 | SELECT bdate, added, removed, count(bdate) AS amount 201 | FROM ( 202 | SELECT bugs.creation_ts::date AS bdate, 'New'::varchar(255) AS added, NULL as removed 203 | FROM bugs WHERE bugs.product_id = ? 204 | UNION ALL 205 | SELECT bugs_activity.bug_when::date as bdate, bugs_activity.added, bugs_activity.removed 206 | FROM bugs_activity JOIN bugs ON (bugs_activity.bug_id = bugs.bug_id AND bugs.product_id = ?) 207 | WHERE fieldid = (SELECT id FROM fielddefs WHERE name = 'bug_status') 208 | ) AS data 209 | GROUP BY bdate, added, removed 210 | ORDER BY bdate, added, removed 211 | }; 212 | 213 | foreach my $rec (@products) { 214 | my $id = @$rec[0]; 215 | my $name = @$rec[1]; 216 | my $bugs = $dbh->selectall_arrayref($query, undef, $id, $id); 217 | } 218 | } 219 | 220 | 1; 221 | -------------------------------------------------------------------------------- /extensions/Reporting/template/en/default/hook/global/user-error-errors.html.tmpl: -------------------------------------------------------------------------------- 1 | [% IF error == "invalid_report_type" %] 2 | [% title = "Invalid Report Type" %] 3 | The chosen report type is invalid. 4 | [% END %] 5 | -------------------------------------------------------------------------------- /extensions/Reporting/template/en/default/pages/reporting.html.tmpl: -------------------------------------------------------------------------------- 1 | [% PROCESS global/variables.none.tmpl %] 2 | [% PROCESS global/header.html.tmpl 3 | title = "Reports" 4 | %] 5 | 6 |
    7 |

    Reports

    8 |

    Different reports representing bug related statistics

    9 | 10 | 18 |
    19 | 20 | [% PROCESS global/footer.html.tmpl %] 21 | -------------------------------------------------------------------------------- /extensions/Reporting/template/en/default/pages/showreport.html.tmpl: -------------------------------------------------------------------------------- 1 | [% PROCESS global/variables.none.tmpl %] 2 | [% PROCESS global/header.html.tmpl 3 | title = report.title 4 | style_urls = [ "extensions/Reporting/web/style.css" ] 5 | %] 6 | 7 |
    8 |

    [% report.title %]

    9 |

    [% report.desc %]

    10 |
    11 | [% FOREACH image IN images %] 12 | 13 | [% END %] 14 |
    15 |
    16 | -------------------------------------------------------------------------------- /extensions/Reporting/web/style.css: -------------------------------------------------------------------------------- 1 | div#graph { 2 | text-align: center; 3 | } 4 | -------------------------------------------------------------------------------- /extensions/SVNLinks/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::SVNLinks; 2 | use strict; 3 | 4 | use constant NAME => 'SVNLinks'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/SVNLinks/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::SVNLinks; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use base qw(Bugzilla::Extension); 7 | 8 | use constant { 9 | SVN_PORTS => "https://svnweb.freebsd.org/changeset/ports/", 10 | SVN_BASE => "https://svnweb.freebsd.org/changeset/base/", 11 | SVN_DOC => "https://svnweb.freebsd.org/changeset/doc/", 12 | GIT_PORTS => "https://cgit.freebsd.org/ports/commit/?id=", 13 | GIT_BASE => "https://cgit.freebsd.org/src/commit/?id=", 14 | GIT_DOC => "https://cgit.freebsd.org/doc/commit/?id=", 15 | PHABRIC => "https://reviews.freebsd.org/", 16 | }; 17 | 18 | our $VERSION = '0.1.0'; 19 | 20 | sub bug_format_comment { 21 | my ($self, $args) = @_; 22 | my $regexes = $args->{regexes}; 23 | 24 | push(@$regexes, { 25 | match => qr/(^|\h+)ports\h+([0-9a-f]{6,40})/im, 26 | replace => \&_link_git_ports 27 | }); 28 | push(@$regexes, { 29 | match => qr/(^|\h+)base\h+([0-9a-f]{6,40})/im, 30 | replace => \&_link_git_base 31 | }); 32 | push(@$regexes, { 33 | match => qr/(^|\h+)doc\h+([0-9a-f]{6,40})/im, 34 | replace => \&_link_git_doc 35 | }); 36 | push(@$regexes, { 37 | match => qr/(^|\h+)ports\h+(?:\#|r)?(\d+)/im, 38 | replace => \&_link_svn_ports 39 | }); 40 | push(@$regexes, { 41 | match => qr/(^|\h+)base\h+(?:\#|r)?(\d+)/im, 42 | replace => \&_link_svn_base 43 | }); 44 | push(@$regexes, { 45 | match => qr/(^|\h+)doc\h+(?:\#|r)?(\d+)/im, 46 | replace => \&_link_svn_doc 47 | }); 48 | push(@$regexes, { 49 | match => qr/(^|\h+)review\h+(D\d+)/im, 50 | replace => \&_link_phabric 51 | }); 52 | } 53 | 54 | sub _link_svn_ports { 55 | my $pre = $1 || ""; 56 | my $rev = $2 || ""; 57 | my $link = $pre . "ports r$rev"; 59 | return $link; 60 | } 61 | 62 | sub _link_svn_base { 63 | my $pre = $1 || ""; 64 | my $rev = $2 || ""; 65 | my $link = $pre . "base r$rev"; 67 | return $link; 68 | } 69 | 70 | sub _link_svn_doc { 71 | my $pre = $1 || ""; 72 | my $rev = $2 || ""; 73 | my $link = $pre . "doc r$rev"; 75 | return $link; 76 | } 77 | 78 | sub _link_git_ports { 79 | my $pre = $1 || ""; 80 | my $sha1 = $2 || ""; 81 | my $link = $pre . "ports $sha1"; 83 | return $link; 84 | } 85 | 86 | sub _link_git_base { 87 | my $pre = $1 || ""; 88 | my $sha1 = $2 || ""; 89 | my $link = $pre . "base $sha1"; 91 | return $link; 92 | } 93 | 94 | sub _link_git_doc { 95 | my $pre = $1 || ""; 96 | my $sha1 = $2 || ""; 97 | my $link = $pre . "doc $sha1"; 99 | return $link; 100 | } 101 | 102 | sub _link_phabric { 103 | my $pre = $1 || ""; 104 | my $rev = $2 || ""; 105 | my $link = $pre . "review $rev"; 107 | return $link; 108 | } 109 | 110 | __PACKAGE__->NAME; 111 | -------------------------------------------------------------------------------- /extensions/SpamDelete/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::SpamDelete; 2 | use strict; 3 | 4 | use constant NAME => 'SpamDelete'; 5 | 6 | use constant REQUIRED_MODULES => [ 7 | ]; 8 | 9 | use constant OPTIONAL_MODULES => [ 10 | ]; 11 | 12 | __PACKAGE__->NAME; 13 | -------------------------------------------------------------------------------- /extensions/SpamDelete/Extension.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::SpamDelete; 2 | 3 | use strict; 4 | use warnings; 5 | use base qw(Bugzilla::Extension); 6 | 7 | use Bugzilla; 8 | use Bugzilla::Bug; 9 | use Bugzilla::Constants; 10 | use Bugzilla::Error; 11 | use Bugzilla::Mailer; 12 | use Bugzilla::Search; 13 | use Bugzilla::User; 14 | use Bugzilla::Util; 15 | use Bugzilla::Extension::BFBSD::Helpers; 16 | 17 | use Date::Parse; 18 | use experimental 'smartmatch'; 19 | 20 | our $VERSION = '0.1.0'; 21 | my @WHITELIST = qw( 22 | github.com 23 | bugs.freebsd.org 24 | freebsd.org 25 | forums.freebsd.org 26 | git.freebsd.org 27 | cgit.freebsd.org 28 | reviews.freebsd.org 29 | lists.freebsd.org 30 | bugs.freebsd.org 31 | docs.freebsd.org 32 | www.freebsd.org 33 | wiki.freebsd.org 34 | svn.freebsd.org 35 | security.freebsd.org 36 | svnweb.freebsd.org 37 | download.freebsd.org 38 | gnu.org 39 | www.gnu.org 40 | bitbucket.org 41 | bz-attachments.freebsd.org 42 | ); 43 | 44 | sub is_white_listed { 45 | my $url = shift; 46 | $url = lc($url); 47 | $url =~ s@^https?://([^/]*).*@$1@; 48 | return ($url ~~ @WHITELIST); 49 | } 50 | 51 | sub install_update_db { 52 | my ($self, $args) = @_; 53 | my $dbh = Bugzilla->dbh; 54 | # 55 | } 56 | 57 | sub config_add_panels { 58 | my ($self, $args) = @_; 59 | my $modules = $args->{panel_modules}; 60 | $modules->{SpamDelete} = "Bugzilla::Extension::SpamDelete::Config"; 61 | } 62 | 63 | sub page_before_template { 64 | my ($self, $args) = @_; 65 | my $page = $args->{page_id}; 66 | 67 | if ($page ne "searchspam.html" && $page ne "deletespam.html" && $page ne "listsuspects.html") { 68 | return; 69 | } 70 | 71 | Bugzilla->login(LOGIN_REQUIRED); 72 | if (!Bugzilla->user->in_group(Bugzilla->params->{spam_delete_group})) { 73 | ThrowUserError("auth_failure", { 74 | group => Bugzilla->params->{spam_delete_group}, 75 | action => "access", 76 | object => "administrative_pages", 77 | }); 78 | } 79 | if ($page eq "searchspam.html") { 80 | _search_user($args); 81 | } elsif ($page eq "listsuspects.html") { 82 | _list_suspects($args); 83 | } elsif ($page eq "deletespam.html") { 84 | _block($args); 85 | } 86 | } 87 | 88 | sub _get_comments_with_link { 89 | my $dbh = Bugzilla->dbh; 90 | my $input = Bugzilla->input_params; 91 | 92 | my $only_suspects = 0; 93 | my $time_limit = "(bug_when > NOW() - INTERVAL '2 weeks')"; 94 | my @bind; 95 | if (defined($input->{year})) { 96 | if ($input->{year} =~ m/^(\d+)$/) { 97 | push @bind, $1; 98 | $time_limit = "(bug_when > NOW() - INTERVAL '7 years')"; 99 | $time_limit = "(EXTRACT(YEAR from bug_when) = ?)"; 100 | $only_suspects = 1; 101 | } 102 | } 103 | my $sth = $dbh->prepare(" 104 | SELECT bug_id, comment_id, who, login_name, bug_when, thetext 105 | FROM longdescs JOIN profiles ON (userid = who) 106 | WHERE $time_limit 107 | AND (thetext ilike '%http://%' or thetext ilike '%https://%') 108 | AND NOT (login_name ilike '%\@freebsd.org') 109 | AND (disabledtext = '') 110 | "); 111 | $sth->execute(@bind); 112 | 113 | my $age_sth = $dbh->prepare(" 114 | SELECT profiles_when 115 | FROM profiles_activity WHERE fieldid=30 and who=? LIMIT 1 116 | "); 117 | 118 | my @comments; 119 | my %registered; 120 | while (my($bug, $comment, $who, $login, $when, $text) = $sth->fetchrow_array) { 121 | my @links; 122 | while ($text =~ m@(https?://.*?)(?:\h|$)@mig) { 123 | push @links, $1; 124 | } 125 | @links = grep { not is_white_listed($_) } @links; 126 | next unless(@links); 127 | if (!defined($registered{$who})) { 128 | $registered{$who} = ''; 129 | $age_sth->execute($who); 130 | if (my ($activity_when) = $age_sth->fetchrow_array) { 131 | $registered{$who} = $activity_when; 132 | } 133 | } 134 | my $age = 'Unknown'; 135 | my $suspect = 0; 136 | if ($registered{$who} ne '') { 137 | my $time_reg = str2time($registered{$who}); 138 | my $time_comment = str2time($when); 139 | my $diff = $time_comment - $time_reg; 140 | if ($diff < 3600) { 141 | $age = int($diff/60); 142 | $age .= ' minutes'; 143 | $suspect = 1; 144 | } 145 | elsif ($diff < 3600*24) { 146 | $age = int($diff/3600); 147 | $age .= ' hours'; 148 | $suspect = 1; 149 | } 150 | elsif ($diff < 3600*24*7) { 151 | $age = int($diff/(3600*24)); 152 | $age .= ' days'; 153 | } 154 | else { 155 | $age = int($diff/(3600*24*7)); 156 | $age .= ' weeks'; 157 | } 158 | } 159 | next if ($only_suspects and not $suspect); 160 | my $entry = { 161 | 'bug' => $bug, 162 | 'when' => $when, 163 | 'who' => $login, 164 | 'registered' => $registered{$who}, 165 | 'age' => $age, 166 | 'suspect' => $suspect, 167 | 'links' => \@links 168 | }; 169 | push @comments, $entry; 170 | } 171 | 172 | return [ sort { $b->{when} cmp $a->{when} } @comments ]; 173 | } 174 | 175 | sub _get_bugs { 176 | my $spamuser = shift(); 177 | 178 | my %criteria = ( 179 | "v1" => $spamuser->login, 180 | "f1" => "commenter", 181 | "o1" => "equals", 182 | "query_format" => "advanced", 183 | ); 184 | my $fields = [ "bug_id", "product", "component", "reporter", "short_desc" ]; 185 | my $search = new Bugzilla::Search( 186 | "fields" => $fields, 187 | "params" => \%criteria, 188 | "user" => Bugzilla->user, 189 | "allow_unlimited" => 1, 190 | "order" => ['bugs.bug_id desc'] 191 | ); 192 | return $search->data; 193 | } 194 | 195 | sub _search_user { 196 | my $args = shift(); 197 | my $vars = $args->{vars}; 198 | my $cgi = Bugzilla->cgi; 199 | 200 | if (!defined($cgi->param("user")) || $cgi->param("user") eq "") { 201 | # No user 202 | return; 203 | } 204 | # Search for a user 205 | my $spamuser = get_user($cgi->param("user")); 206 | if (!$spamuser) { 207 | ThrowUserError("invalid_user", { user_login => $cgi->param("user") }); 208 | } 209 | 210 | $vars->{bugs} = _get_bugs($spamuser); 211 | } 212 | 213 | sub _list_suspects { 214 | my $args = shift(); 215 | my $vars = $args->{vars}; 216 | my $cgi = Bugzilla->cgi; 217 | 218 | $vars->{comments} = _get_comments_with_link(); 219 | } 220 | 221 | sub _block { 222 | my $args = shift(); 223 | my $vars = $args->{vars}; 224 | my $input = Bugzilla->input_params; 225 | 226 | if (!defined($input->{user}) || $input->{user} eq "") { 227 | ThrowUserError("invalid_user", { user_login => $input->{user} }); 228 | } 229 | 230 | my $curuser = switch_to_automation(); 231 | return if !defined($curuser); 232 | 233 | # Search for a user 234 | my $spamuser = get_user($input->{user}); 235 | if (!$spamuser) { 236 | Bugzilla->set_user($curuser); 237 | ThrowUserError("invalid_user", { user_login => $input->{user} }); 238 | } 239 | if ($spamuser->in_group(Bugzilla->params->{spam_delete_group})) { 240 | # Users of the spam deletion group can't be deleted. 241 | Bugzilla->set_user($curuser); 242 | ThrowUserError("invalid_user", { user_login => $input->{user} }); 243 | } 244 | 245 | $vars->{spamuser} = $spamuser->login; 246 | 247 | # Block the user 248 | if ($input->{action} eq "Block User") { 249 | $spamuser->set_disabledtext('[SPAM] ' . Bugzilla->params->{spam_disable_text}); 250 | $spamuser->update(); 251 | } 252 | 253 | _send_info($curuser->login, $spamuser->login); 254 | Bugzilla->set_user($curuser); 255 | } 256 | 257 | sub _send_info { 258 | my ($who, $spamuser) = @_; 259 | my $mail = " 260 | From: bugzilla-noreply\@FreeBSD.org 261 | To: %s 262 | Subject: Spammer blocked on %s 263 | 264 | Dear administrators, 265 | 266 | User %s blocked the potential spammer 267 | %s on %s. 268 | 269 | User's PRs/comments: 270 | %spage.cgi?id=searchspam.html&action=search&user=%s 271 | "; 272 | 273 | my $to = Bugzilla->params->{maintainer}; 274 | my @contacts = split(",", Bugzilla->params->{spam_contacts}); 275 | foreach my $c (@contacts) { 276 | $c =~ s/^\s+|\s+$//g; 277 | $to .= ",$c"; 278 | } 279 | 280 | my $mailmsg = sprintf( 281 | $mail, 282 | $to, 283 | Bugzilla->params->{urlbase}, 284 | $who, 285 | $spamuser, 286 | Bugzilla->params->{urlbase}, 287 | Bugzilla->params->{urlbase}, 288 | url_quote($spamuser), 289 | ); 290 | 291 | MessageToMTA($mailmsg, 1); 292 | } 293 | 294 | __PACKAGE__->NAME; 295 | -------------------------------------------------------------------------------- /extensions/SpamDelete/lib/Config.pm: -------------------------------------------------------------------------------- 1 | package Bugzilla::Extension::SpamDelete::Config; 2 | 3 | use strict; 4 | use warnings; 5 | 6 | use Bugzilla::Config::Common; 7 | use Bugzilla::Group; 8 | 9 | our $sortkey = 5000; 10 | 11 | sub get_param_list { 12 | my ($class) = @_; 13 | 14 | my @param_list = ( 15 | { 16 | name => "spam_backupfolder", 17 | type => "t", 18 | default => "spam", 19 | }, 20 | { 21 | name => "spam_contacts", 22 | type => "t", 23 | default => "", 24 | checker => \&check_email, 25 | }, 26 | { 27 | name => "spam_delete_group", 28 | type => "s", 29 | choices => \&_get_groups, 30 | default => 'admin', 31 | checker => \&check_group 32 | }, 33 | { 34 | name => "spam_disable_text", 35 | type => "l", 36 | default => 37 | "This account has been disabled as a " . 38 | "result of creating spam." 39 | }, 40 | ); 41 | return @param_list; 42 | } 43 | 44 | sub _get_groups { 45 | my @groups = map {$_->name} Bugzilla::Group->get_all; 46 | unshift(@groups, ''); 47 | return \@groups; 48 | } 49 | 50 | 1; 51 | -------------------------------------------------------------------------------- /extensions/SpamDelete/template/en/default/admin/params/spamdelete.html.tmpl: -------------------------------------------------------------------------------- 1 | [% 2 | title = "Spam Deletion" 3 | desc = "Configure the Spam Deletion" 4 | %] 5 | 6 | [% param_descs = { 7 | spam_backupfolder => 8 | "This is the root folder for backups of deleted bugs. " _ 9 | "Each bug that is deleted, will be stored as text dump " _ 10 | "file 'folder/spam_deleted_bug_id' into that folder, " _ 11 | "e.g. 'spam/spam_bug_192664'. The folder's location is relative " _ 12 | "to Bugzilla's data directory.", 13 | spam_contacts => 14 | "A comma-separated list of email adresses, which " _ 15 | "shall be notified about a user being blocked by " _ 16 | "the Spam Deletion extension, besides the " _ 17 | "maintainer. The email will contain " _ 18 | "the login of the user being blocked and a brief " _ 19 | "overview about the deleted bugs.", 20 | spam_delete_group => 21 | "Users in this group will be allowed to mark other users as " _ 22 | "spammers.", 23 | spam_disable_text => 24 | "This message will be displayed to the user when they try to log " _ 25 | "in after their account has been disabled by the Spam Deletion " _ 26 | "extension." 27 | } 28 | %] 29 | -------------------------------------------------------------------------------- /extensions/SpamDelete/template/en/default/hook/bug/edit-after_custom_fields.html.tmpl: -------------------------------------------------------------------------------- 1 | [% RETURN UNLESS user.in_group(Bugzilla.params.spam_delete_group) %] 2 | [% RETURN UNLESS user.login != bug.reporter.login %] 3 | [% RETURN UNLESS !bug.reporter.in_group(Bugzilla.params.spam_delete_group) %] 4 | 5 | 6 | 7 | 8 | Mark Reporter as Spammer 9 | 10 | 11 | -------------------------------------------------------------------------------- /extensions/SpamDelete/template/en/default/hook/global/common-links-fbsd-links.html.tmpl: -------------------------------------------------------------------------------- 1 | [% RETURN UNLESS user.login %] 2 | [% RETURN UNLESS user.in_group(Bugzilla.params.spam_delete_group) %] 3 |
  • 4 | | 5 | Spam 6 |
  • 7 | -------------------------------------------------------------------------------- /extensions/SpamDelete/template/en/default/pages/deletespam.html.tmpl: -------------------------------------------------------------------------------- 1 | [% PROCESS global/variables.none.tmpl %] 2 | [% PROCESS global/header.html.tmpl 3 | title = "Block Spammers" 4 | style_urls = [ "extensions/SpamDelete/web/style.css", 5 | "skins/standard/buglist.css"] 6 | %] 7 | 8 |
    9 |

    Spammer blocked

    10 |

    The user [% spamuser %] was blocked and their PRs/comments were marked as spam.

    11 |

    Back to search page

    12 |

    13 | -------------------------------------------------------------------------------- /extensions/SpamDelete/template/en/default/pages/listsuspects.html.tmpl: -------------------------------------------------------------------------------- 1 | [% PROCESS global/variables.none.tmpl %] 2 | [% PROCESS global/header.html.tmpl 3 | title = "Comments With Links" 4 | style_urls = [ "extensions/SpamDelete/web/style.css", 5 | "skins/standard/buglist.css"] 6 | %] 7 | 8 |
    9 |

    Comments with links

    10 |

    11 | The following page lists comments posted that contain links and 12 | were posted last two weeks. 13 |

    14 |
    15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | [% FOREACH comment = comments %] 26 | 27 | 31 | 32 | 33 | 38 | 39 | [% END %] 40 | 41 |
    WhoWhenBugLinks
    [% comment.who %]
    28 | Diff: [% comment.age %]
    29 | All comments 30 |
    [% comment.when %][% comment.bug %] 34 | [% FOREACH link = comment.links %] 35 | [% link %]
    36 | [% END %] 37 |
    42 |
    43 |
    44 | -------------------------------------------------------------------------------- /extensions/SpamDelete/template/en/default/pages/searchspam.html.tmpl: -------------------------------------------------------------------------------- 1 | [% PROCESS global/variables.none.tmpl %] 2 | [% PROCESS global/header.html.tmpl 3 | title = "Block Spammers" 4 | style_urls = [ "extensions/SpamDelete/web/style.css", 5 | "skins/standard/buglist.css"] 6 | %] 7 | 8 |
    9 |

    Block Spammers

    10 |

    11 | The following page lets you block users, who create unsolicited bug 12 | reports, such as advertisements (AKA spam). The users will be 13 | blocked (not deleted), and all bugs reported and comments posted 14 | by them will be marked as spam. 15 |

    16 | 31 | [% IF Bugzilla.cgi.param("user") %] 32 |
    33 |

    The following bugs were filed or commented by [% Bugzilla.cgi.param("user") FILTER html %] and will be marked as spam.

    34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | [% FOREACH bug = bugs %] 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | [% END %] 54 | 55 |
    IdProductComponentReporterDescription
    [% bug.0 %][% bug.1 %][% bug.2 %][% bug.3 %][% bug.4 %]
    56 |

    57 | Do you really want to block the user and delete all bug 58 | reports? 59 |

    60 |
    61 | 62 | 64 | 65 |
    66 | 67 |
    68 | [% END %] 69 |
    70 | -------------------------------------------------------------------------------- /extensions/SpamDelete/web/style.css: -------------------------------------------------------------------------------- 1 | table#deletebugs { 2 | width: 100%; 3 | border: 1px solid #DDD; 4 | background-color: #EEE; 5 | border-collapse: collapse; 6 | } 7 | 8 | .header { 9 | font-weight: bold; 10 | text-align: left; 11 | } 12 | 13 | th.expand { 14 | width: 99%; 15 | } 16 | 17 | tr th, tr td { 18 | white-space: nowrap; 19 | padding: 0.5em; 20 | } -------------------------------------------------------------------------------- /images/committer.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/images/committer.png -------------------------------------------------------------------------------- /images/fbsd_favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/images/fbsd_favicon.ico -------------------------------------------------------------------------------- /images/googlesoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/images/googlesoc.png -------------------------------------------------------------------------------- /images/triager.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/images/triager.png -------------------------------------------------------------------------------- /monthlyexprun.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -w 2 | 3 | use strict; 4 | use warnings; 5 | use lib qw(. lib); 6 | 7 | use Bugzilla; 8 | use Bugzilla::Mailer; 9 | use Bugzilla::Constants; 10 | use Bugzilla::Util; 11 | use Email::MIME; 12 | 13 | my $urlbase = Bugzilla->params->{'urlbase'}; 14 | 15 | my $MAIL_TXT_HEADER = "To view an individual PR, use: 16 | ${urlbase}show_bug.cgi?id=(Bug Id). 17 | "; 18 | 19 | my $MAIL_HTML_HEADER = '
    ';
     20 | 
     21 | my $MAIL_COMMON = "
     22 | The following is a list of exp-runs completed during the last month.
     23 | 
     24 | Date       |    Bug Id | Description
     25 | -----------+-----------+---------------------------------------------------
     26 | %s
     27 | %d exp-runs completed during the last month
     28 | ";
     29 | 
     30 | my $MAIL_TXT_FOOTER = "";
     31 | 
     32 | my $MAIL_HTML_FOOTER = '
    '; 33 | 34 | my $TBLROW = "%-10s | %9s | %-50.49s\n"; 35 | my $TBLROW_HTML = "%-10s | %9s | %s\n"; 36 | 37 | # We're a non-interactive user 38 | Bugzilla->usage_mode(USAGE_MODE_CMDLINE); 39 | # Get the db conection and query the db directly. 40 | my $dbh = Bugzilla->dbh; 41 | 42 | # Report on flags changed in the last month 43 | my $first = $dbh->sql_date_math("date_trunc('month', NOW())", '-', '1', 'MONTH'); 44 | my $last = "date_trunc('month', NOW())"; 45 | 46 | my $query = q{ 47 | SELECT DISTINCT 48 | bugs.bug_id, bugs.short_desc, bugs.bug_status, 49 | TO_CHAR(flags.modification_date::date, 'YYYY-MM-DD') AS modification_date 50 | FROM 51 | bugs 52 | JOIN flags ON (bugs.bug_id = flags.bug_id AND flags.status = '+') 53 | JOIN flagtypes ON (flags.type_id = flagtypes.id) 54 | WHERE 55 | flagtypes.name IN (?) AND } . 56 | $dbh->sql_to_days("modification_date") . " >= " . 57 | $dbh->sql_to_days("(".$first.")") . " AND " . 58 | $dbh->sql_to_days("modification_date") . " < " . 59 | $dbh->sql_to_days("(".$last.")") . 60 | " ORDER BY modification_date, bugs.bug_id;"; 61 | 62 | my $bugs = $dbh->selectall_arrayref( 63 | $query, 64 | undef, 65 | 'exp-run'); 66 | 67 | # Prepare report for email 68 | my $tblbugs = ""; 69 | my $tblbugs_html = ""; 70 | my $bugcount = scalar(@$bugs); 71 | foreach my $bug (@$bugs) { 72 | my ($id, $desc, $status, $modified) = @$bug; 73 | $tblbugs .= sprintf($TBLROW, $modified, $id, $desc); 74 | my $desc_cut = html_quote(substr($desc, 0, 49)); 75 | my $url = "${urlbase}show_bug.cgi?id=$id"; 76 | $tblbugs_html .= sprintf($TBLROW_HTML, $modified, $url, $id, $url, $desc_cut); 77 | $tblbugs_html =~ s/()(\s+)/$2$1/; 78 | } 79 | 80 | my $body_txt = $MAIL_TXT_HEADER; 81 | $body_txt .= sprintf($MAIL_COMMON, $tblbugs, $bugcount); 82 | $body_txt .= $MAIL_TXT_FOOTER; 83 | 84 | my $body_html = $MAIL_HTML_HEADER; 85 | $body_html .= sprintf($MAIL_COMMON, $tblbugs_html, $bugcount); 86 | $body_html .= $MAIL_HTML_FOOTER; 87 | 88 | # parts should go from least rich to most rich format 89 | my @parts = ( 90 | Email::MIME->create( 91 | attributes => { 92 | content_type => "text/plain", 93 | charset => "UTF-8" 94 | }, 95 | body => $body_txt 96 | ), 97 | Email::MIME->create( 98 | attributes => { 99 | content_type => "text/html", 100 | charset => "UTF-8" 101 | }, 102 | body => $body_html 103 | ), 104 | ); 105 | 106 | my $mailmsg = Email::MIME->create( 107 | header_str => [ 108 | From => 'bugzilla-noreply@FreeBSD.org', 109 | To => [ 'portmgr@FreeBSD.org' ], 110 | Subject => "Exp-runs completed during the last month", 111 | ], 112 | attributes => { 113 | content_type => "multipart/alternative" 114 | }, 115 | parts => [ @parts ], 116 | ); 117 | 118 | # Send the mail via the bugzilla configuration. 119 | #print $mailmsg->as_string; 120 | MessageToMTA($mailmsg, 1); 121 | -------------------------------------------------------------------------------- /news.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# News file for the FreeBSD bugzilla instance #%] 2 | [% news = [ 3 | { 4 | date => '2025-01-04', 5 | text => '' 11 | }, 12 | { 13 | date => '2024-11-23', 14 | text => '
      15 |
    • Version 14.2-RELEASE of product Base System was created.
    • 16 |
    ' 17 | }, 18 | { 19 | date => '2024-11-03', 20 | text => '
      21 |
    • Version 14.2-STABLE of product Base System was created.
    • 22 |
    ' 23 | }, 24 | { 25 | date => '2024-08-02', 26 | text => '
      27 |
    • Version 13.4-STABLE of product Base System was created.
    • 28 |
    ' 29 | }, 30 | { 31 | date => '2024-07-01', 32 | text => '
      33 |
    • As support for them have ended, Base System versions 13.2-* were 34 | disabled for new bugs.
    • 35 |
    • Belatedly, version 13.3-STABLE of product Base System was created.
    • 36 |
    ' 37 | }, 38 | { 39 | date => '2024-06-15', 40 | text => '
      41 |
    • Versions 14.1-RELEASE and 14.1-STABLE of product Base System were 42 | created.
    • 43 |
    • As an experiment, the keyword "vendor" has been created. Set this 44 | to flag a PR as filed from an upstream or downstream of FreeBSD.
    • 45 |
    ' 46 | }, 47 | { 48 | date => '2024-01-20', 49 | text => '
      50 |
    • As an experiment, the flag "needs-errata" has been created. Set this 51 | to ? to ask Release Engineering or Secteam to evaluate whether an 52 | Errata Notice 53 | should be created for an existing release.
    • 54 |
    ' 55 | }, 56 | { 57 | date => '2024-01-04', 58 | text => '
      59 |
    • As support for it has ended, Base System version 12.4 was disabled 60 | for new bugs.
    • 61 |
    • The weekly reminder script was updated to deselect mfc-stable12.
    • 62 |
    • The Dashboard MFC query was similarly updated.
    • 63 |
    ' 64 | }, 65 | { 66 | date => '2023-12-17', 67 | text => '
      68 |
    • The weekly reminder script was belatedly updated to: 69 |
        70 |
      • deselect mfc-stable8 through 11.
      • 71 |
      • select mfc-stable12 through 14.
      • 72 |
      73 |
    • The Dashboard MFC query was belatedly fixed as above.
    • 74 |
    • Note that mfc-stable12 will be removed 20240101.
    • 75 |
    ' 76 | }, 77 | { 78 | date => '2023-11-30', 79 | text => '
      80 |
    • A stale query was removed from the Dashboard (PR 215844).
    • 81 |
    • max_search_results was updated from 10000 to 12500. 82 | This was limiting the totals from certain queries.
    • 83 |
    ' 84 | }, 85 | { 86 | date => '2023-08-30', 87 | text => '
      88 |
    • To reflect the prelease state of FreeBSD 14.0, the following were 89 | done: 90 |
        91 |
      • Version 14.0-STABLE of product Base System was created.
      • 92 |
      • Version 15.0-CURRENT of product Base System was created.
      • 93 |
      • The flag type mfc-stable14 was created.
      • 94 |
      95 |
    • As support for them has ended, Base System versions 12.0-*, 12.1-*, 96 | 12.2-*, 12.3-*, 13.0-*, and 13.1-* were disabled for new bugs.
    • 97 |
    ' 98 | }, 99 | { 100 | date => '2021-01-24', 101 | text => '
      102 |
    • To reflect the prelease state of FreeBSD 13.0, the following were 103 | done: 104 |
        105 |
      • Version 13.0-STABLE of product Base System was created.
      • 106 |
      • The flag type mfc-stable13 was created.
      • 107 |
      108 |
    • As support for them has ended, Base System versions 12.0-RELEASE 109 | and 12.0-STABLE were disabled for new bugs.
    • 110 |
    • Hardware value sparc64 has been disabled for new bugs.
    • 111 |
    ' 112 | }, 113 | { 114 | date => '2020-07-12', 115 | text => '
      116 |
    • In order to lay the groundwork for future changes/improvements, 117 | and to make it easier to search for things that are not assigned, 118 | we have modified the Real Name of all bugzilla accounts 119 | that are default assignees as such:
      120 |     Assignee: freebsd-net (Nobody).
      121 | This is to make it more obvious that default assignees are assigned to 122 | Nobody and encourages self-assignment (to real people) to 123 | make resolution more consistent.
    • 124 |
    ' 125 | }, 126 | { 127 | date => '2020-07-11', 128 | text => '
      129 |
    • To help support the 202007 Bug Squash, all of the PRs containing 130 | patches were audited so that: 131 |
        132 |
      • Each attachment that has its Patch flag set has 133 | been verified to contain a patch.
      • 134 |
      • If an attachment filename ends in .diff or 135 | .patch, its Patch flag has been 136 | verified to be set.
      • 137 |
      • The remaining attachment filenames end in .shar.
      • 138 |
      139 |
    • 140 |
    • This officially obsoletes the use of the the Keywords 141 | field value of patch. This value had to be added by hand 142 | and as a result was applied inconsistently.
    • 143 |
    ' 144 | }, 145 | ] 146 | %] 147 | 148 | [% FOREACH item IN news %] 149 | [% BREAK IF loop.count() > 5 %] 150 |
    151 | [% item.date FILTER html %] 152 |

    [% item.text %]

    153 |
    154 | [% END %] 155 | -------------------------------------------------------------------------------- /skins/contrib/FreeBSD/global.css: -------------------------------------------------------------------------------- 1 | body { 2 | font-family: Verdana, sans-serif; 3 | background-image: none; 4 | background-color: #fff; 5 | color: #000; 6 | } 7 | 8 | h1, h2, h3, h4, h5 { 9 | color: #900; 10 | } 11 | 12 | h1 { 13 | font-size: 1.4em; 14 | } 15 | 16 | h2 { 17 | font-size: 1.2em; 18 | } 19 | 20 | h3 { 21 | font-size: 1.1em; 22 | font-weight: bold; 23 | } 24 | 25 | a { 26 | color: #900; 27 | } 28 | a:visited { 29 | color: #400; 30 | } 31 | a:hover { 32 | color: #000; 33 | } 34 | 35 | .rss { 36 | line-height: 1.5em; 37 | padding: 0; 38 | } 39 | .rss:after { 40 | content: url('../../../images/rss_small.png'); 41 | } 42 | 43 | /* header and footer */ 44 | #titles { 45 | background-color: #900; 46 | } 47 | .links li a { 48 | color: #666; 49 | text-decoration: none; 50 | font-weight: bold; 51 | } 52 | .links li a:hover { 53 | color: #900; 54 | } 55 | #links-saved .links li a { 56 | font-weight: normal; 57 | } 58 | #header .links { 59 | background-color: #eee; 60 | border-left: 1px solid #ddd; 61 | border-right: 1px solid #ddd; 62 | border-bottom: 1px solid #ddd; 63 | border-radius: 5px; 64 | } 65 | #footer { 66 | background-color: #eee; 67 | border: 1px solid #ddd; 68 | border-radius: 5px; 69 | } 70 | 71 | /* Input elements */ 72 | form input { 73 | border: 1px solid #DADADA; 74 | } 75 | form input[type="submit"] { 76 | background-color: #eee; 77 | } 78 | select { 79 | border: 1px solid #DADADA; 80 | } 81 | 82 | .bz_comment { 83 | border: 1px solid #ddd; 84 | border-radius: 0.25em; 85 | border-collapse: collapse; 86 | } 87 | 88 | /* Bug comments */ 89 | .bz_comment_head, .bz_first_comment_head { 90 | padding-top: .5em; 91 | padding-bottom: .5em; 92 | } 93 | .bz_comment_text { 94 | padding-left: .5em; 95 | padding-right: .5em; 96 | } 97 | 98 | /* Attachments */ 99 | 100 | #attachment_table { 101 | width: 100%; 102 | } 103 | #attachment_table th, .bz_attach_footer { 104 | background-color: #eee; 105 | } 106 | 107 | /** 108 | * The bugzilla authors use a table to align a button on several forms 109 | * within the preferences - we can't simply format tables our way... 110 | */ 111 | /* 112 | table { 113 | background-color: #EEE; 114 | border-spacing: 0px; 115 | border-width: 1px 1px 0px; 116 | border-style: solid solid none; 117 | border-color: #CACACA #CACACA; 118 | border-image: none; 119 | border-collapse: collapse; 120 | } 121 | */ 122 | -------------------------------------------------------------------------------- /skins/contrib/FreeBSD/index.css: -------------------------------------------------------------------------------- 1 | /* Index page */ 2 | #page-index { 3 | max-width: 100%; 4 | margin-left: 5em; 5 | margin-right: 5em; 6 | margin-bottom: 1em; 7 | } 8 | #left-index { 9 | float: left; 10 | width: 60%; 11 | } 12 | #right-index { 13 | width: 35%; 14 | float: right; 15 | } 16 | #welcome { 17 | text-align: inherit; 18 | } 19 | .fbsd_quick_actions ul { 20 | list-style-type: none; 21 | padding-left: 3em; 22 | } 23 | .fbsd_quick_actions ul li { 24 | margin-bottom: 1em; 25 | } 26 | 27 | .fbsd_quick_actions ul li a { 28 | font-weight: bold; 29 | text-decoration: none; 30 | } 31 | .fbsd_quick_actions ul li a:visited { 32 | color: #900; 33 | } 34 | .fbsd_quick_actions ul li a:hover { 35 | color: #000; 36 | } 37 | #query, #account, #enter_bug { 38 | background: none; 39 | } 40 | 41 | /* Search form */ 42 | div#quicksearch { 43 | padding-left: 3em; 44 | } 45 | 46 | form#quicksearchForm { 47 | display: inline-block; 48 | text-align: inherit; 49 | margin: 0; 50 | padding: 0; 51 | } 52 | ul#quicksearch_links { 53 | margin-top: 0.25em; 54 | margin-bottom: 0; 55 | text-align: center; 56 | } 57 | 58 | /* News feed */ 59 | .news { 60 | font-size: 85%; 61 | } 62 | 63 | .date { 64 | font-weight: bold; 65 | } -------------------------------------------------------------------------------- /skins/contrib/FreeBSD/index/file-a-bug.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/skins/contrib/FreeBSD/index/file-a-bug.png -------------------------------------------------------------------------------- /skins/contrib/FreeBSD/index/help.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/skins/contrib/FreeBSD/index/help.png -------------------------------------------------------------------------------- /skins/contrib/FreeBSD/index/new-account.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/skins/contrib/FreeBSD/index/new-account.png -------------------------------------------------------------------------------- /skins/contrib/FreeBSD/index/search.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/freebsd/bugzilla/8ce8c83dcf3c3b883dcdbbe2ed93a9d484d2e53f/skins/contrib/FreeBSD/index/search.png -------------------------------------------------------------------------------- /sync_freebsd_committers.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -T 2 | # This Source Code Form is subject to the terms of the Mozilla Public 3 | # License, v. 2.0. If a copy of the MPL was not distributed with this 4 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 5 | # 6 | # This Source Code Form is "Incompatible With Secondary Licenses", as 7 | # defined by the Mozilla Public License, v. 2.0. 8 | 9 | use 5.10.1; 10 | use strict; 11 | use warnings; 12 | 13 | use lib qw(. lib); 14 | 15 | use Net::LDAP; 16 | use Bugzilla; 17 | use Bugzilla::User; 18 | 19 | my $quiet = 0; 20 | my $dbh = Bugzilla->dbh; 21 | 22 | my %ldap_users; 23 | 24 | ### 25 | # Get current bugzilla users 26 | ### 27 | my %bugzilla_users = %{ $dbh->selectall_hashref( 28 | 'SELECT lower(login_name) AS new_login_name, userid, realname, disabledtext ' . 29 | 'FROM profiles', 'new_login_name') }; 30 | 31 | foreach my $login_name (keys %bugzilla_users) { 32 | # remove whitespaces 33 | $bugzilla_users{($login_name)}{'realname'} =~ s/^\s+|\s+$//g; 34 | } 35 | 36 | ### 37 | # Get current LDAP users 38 | ### 39 | my $LDAPserver = Bugzilla->params->{"LDAPserver"}; 40 | if ($LDAPserver =~ ",") { 41 | $LDAPserver =~ s/,.*//; 42 | } 43 | if ($LDAPserver eq "") { 44 | print "No LDAP server defined in bugzilla preferences.\n"; 45 | exit; 46 | } 47 | 48 | my $LDAPconn; 49 | if($LDAPserver =~ /:\/\//) { 50 | # if the "LDAPserver" parameter is in uri scheme 51 | $LDAPconn = Net::LDAP->new($LDAPserver, version => 3); 52 | } else { 53 | my $LDAPport = "389"; # default LDAP port 54 | if($LDAPserver =~ /:/) { 55 | ($LDAPserver, $LDAPport) = split(":",$LDAPserver); 56 | } 57 | $LDAPconn = Net::LDAP->new($LDAPserver, port => $LDAPport, version => 3); 58 | } 59 | 60 | if(!$LDAPconn) { 61 | print "Connecting to LDAP server failed. Check LDAPserver setting.\n"; 62 | exit; 63 | } 64 | my $mesg; 65 | if (Bugzilla->params->{"LDAPbinddn"}) { 66 | my ($LDAPbinddn,$LDAPbindpass) = split(":",Bugzilla->params->{"LDAPbinddn"}); 67 | $mesg = $LDAPconn->bind($LDAPbinddn, password => $LDAPbindpass); 68 | } 69 | else { 70 | $mesg = $LDAPconn->bind(); 71 | } 72 | if($mesg->code) { 73 | print "Binding to LDAP server failed: " . $mesg->error . "\nCheck LDAPbinddn setting.\n"; 74 | exit; 75 | } 76 | 77 | # We've got our anonymous bind; let's look up the users. 78 | $mesg = $LDAPconn->search( base => Bugzilla->params->{"LDAPBaseDN"}, 79 | scope => "sub", 80 | filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"} . "=*)" . Bugzilla->params->{"LDAPfilter"} . '(objectClass=freebsdAccount)(gidNumber=493))', 81 | ); 82 | 83 | 84 | if(! $mesg->count) { 85 | print "LDAP lookup failure. Check LDAPBaseDN setting.\n"; 86 | exit; 87 | } 88 | 89 | my %val = %{ $mesg->as_struct }; 90 | 91 | while( my ($key, $value) = each(%val) ) { 92 | 93 | my @login_name = @{ $value->{"uid"} }; 94 | my @realname = @{ $value->{"cn"} }; 95 | 96 | # no mail entered? go to next 97 | if(! @login_name) { 98 | print "$key has no valid mail address\n"; 99 | next; 100 | } 101 | 102 | # no cn entered? use uid instead 103 | if(! @realname) { 104 | print "$key has no real name\n"; 105 | next; 106 | } 107 | 108 | my $login = shift @login_name; 109 | my $real = shift @realname; 110 | utf8::decode($real); 111 | $login .= '@FreeBSD.org'; 112 | $ldap_users{$login} = { realname => $real }; 113 | } 114 | 115 | my %create_users; 116 | 117 | while( my ($key, $value) = each(%ldap_users) ) { 118 | $key = lc($key); 119 | if(!defined $bugzilla_users{$key}){ 120 | next if ($value->{realname} =~ m/\s+user\s*$/i); 121 | $create_users{$key} = $value; 122 | } 123 | } 124 | 125 | while( my ($key, $value) = each(%create_users) ) { 126 | $key =~ s/freebsd.org/FreeBSD.org/ig; 127 | my $realname = $value->{'realname'}; 128 | print "New committer: $realname <$key>\n"; 129 | Bugzilla::User->create({ 130 | login_name => $key, 131 | realname => $value->{'realname'}, 132 | cryptpassword => '*'}); 133 | } 134 | -------------------------------------------------------------------------------- /template/en/custom/global/choose-product.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [%# INTERFACE: 10 | # classifications: array of hashes, with an 'object' key representing a 11 | # classification object and 'products' the list of 12 | # product objects the user can enter bugs into. 13 | # target: the script that displays this template. 14 | #%] 15 | 16 | [% IF target == "enter_bug.cgi" %] 17 | [% title = "Enter $terms.Bug" %] 18 | [% subheader = "Select Product" %] 19 | [% h2 = BLOCK %] 20 | [% IF Param('useclassification') %]Next[% ELSE %]First[% END %], 21 | you must pick a product on which to enter [% terms.abug %]: 22 | [% END %] 23 | [% ELSIF target == "describecomponents.cgi" %] 24 | [% title = "Browse" %] 25 | [% h2 = "Select a product category to browse:" %] 26 | [% END %] 27 | 28 | [% DEFAULT title = "Choose a Product" %] 29 | [% PROCESS global/header.html.tmpl %] 30 | 31 | [% USE Bugzilla %] 32 | [% previous_params = Bugzilla.cgi.canonicalise_query('classification', 'product') %] 33 | 34 |

    [% h2 FILTER html %]

    35 | 36 | 37 | 38 | [% FOREACH c = classifications %] 39 | [% IF c.object %] 40 | 41 | 43 | 44 | [% END %] 45 | 46 | [% FOREACH p = c.products %] 47 | 48 | 54 | 55 | 56 | 57 | [% END %] 58 | 59 | 60 | 61 | 62 | [% END %] 63 | 64 |
    [% c.object.name FILTER html %]: 42 | [%+ c.object.description FILTER html_light %]
    49 | 52 | [% p.name FILTER html %]: 53 | [% p.description FILTER html_light %]
     
    65 | 66 | [% PROCESS global/footer.html.tmpl %] 67 | -------------------------------------------------------------------------------- /template/en/custom/global/common-links.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [% DEFAULT qs_suffix = "" %] 10 | [% USE Bugzilla %] 11 | 12 | 88 | 89 | [% Hook.process("link-row") %] 90 | [% BLOCK link_to_documentation %] 91 | [% IF doc_section %] 92 |
  • 93 | | 94 | Help 95 |
  • 96 | [% END %] 97 | [% END %] 98 | -------------------------------------------------------------------------------- /template/en/custom/global/header.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [%# INTERFACE: 10 | # (All the below interface elements are optional.) 11 | # title: string. Page title. 12 | # header: string. Main page header. 13 | # subheader: string. Page subheader. 14 | # header_addl_info: string. Additional header information. 15 | # bodyclasses: array of extra CSS classes for the 16 | # onload: string. JavaScript code to run when the page finishes loading. 17 | # javascript: string. Javascript to go in the header. 18 | # javascript_urls: list. List of URLs to Javascript. 19 | # style: string. CSS style. 20 | # style_urls: list. List of URLs to CSS style sheets. 21 | # message: string. A message to display to the user. May contain HTML. 22 | # atomlink: Atom link URL, May contain HTML 23 | #%] 24 | 25 | [% IF message %] 26 | [% PROCESS global/messages.html.tmpl %] 27 | [% END %] 28 | 29 | [% DEFAULT 30 | subheader = "" 31 | header_addl_info = "" 32 | onload = "" 33 | style_urls = [] 34 | yui = [] 35 | %] 36 | 37 | [% SET yui_css = { 38 | autocomplete => 1, 39 | calendar => 1, 40 | datatable => 1, 41 | button => 1, 42 | } %] 43 | 44 | [%# Note: This is simple dependency resolution--you can't have dependencies 45 | # that depend on each other. You have to specify all of a module's deps, 46 | # if that module is going to be specified in "yui". 47 | #%] 48 | [% SET yui_deps = { 49 | autocomplete => ['json', 'connection', 'datasource'], 50 | datatable => ['json', 'connection', 'datasource', 'element'], 51 | } %] 52 | 53 | [%# When using certain YUI modules, we need to process certain 54 | # extra JS templates. 55 | #%] 56 | [% SET yui_templates = { 57 | datatable => ['global/value-descs.js.tmpl'], 58 | } %] 59 | 60 | [%# These are JS URLs that are *always* on the page and come before 61 | # every other JS URL. 62 | #%] 63 | [% SET starting_js_urls = [ 64 | "js/yui/yahoo-dom-event/yahoo-dom-event.js", 65 | "js/yui/cookie/cookie-min.js", 66 | ] %] 67 | 68 | 69 | [%# We should be able to set the default value of the header variable 70 | # to the value of the title variable using the DEFAULT directive, 71 | # but that doesn't work if a caller sets header to the empty string 72 | # to avoid header inheriting the value of title, since DEFAULT 73 | # mistakenly treats empty strings as undefined and gives header the 74 | # value of title anyway. To get around that problem we explicitly 75 | # set header's default value here only if it is undefined. %] 76 | [% IF !header.defined %][% header = title %][% END %] 77 | 78 | 79 | 80 | 81 | [% Hook.process("start") %] 82 | [% title %] 83 | 84 | [% IF Param('utf8') %] 85 | 86 | [% END %] 87 | 88 | [% SET yui = yui_resolve_deps(yui, yui_deps) %] 89 | 90 | [% SET css_sets = css_files(style_urls, yui, yui_css) %] 91 | [% IF constants.CONCATENATE_ASSETS %] 92 | [% PROCESS format_css_link asset_url = css_sets.unified_standard_skin %] 93 | [% ELSE %] 94 | [% FOREACH asset_url = css_sets.standard %] 95 | [% PROCESS format_css_link %] 96 | [% END %] 97 | [% FOREACH asset_url = css_sets.skin %] 98 | [% PROCESS format_css_link %] 99 | [% END %] 100 | [% END %] 101 | 102 | [% IF style %] 103 | 106 | [% END %] 107 | 108 | [% IF css_sets.unified_custom %] 109 | [% IF constants.CONCATENATE_ASSETS %] 110 | [% PROCESS format_css_link asset_url = css_sets.unified_custom %] 111 | [% ELSE %] 112 | [% FOREACH asset_url = css_sets.custom %] 113 | [% PROCESS format_css_link %] 114 | [% END %] 115 | [% END %] 116 | [% END %] 117 | 118 | [%# YUI Scripts %] 119 | [% FOREACH yui_name = yui %] 120 | [% starting_js_urls.push("js/yui/$yui_name/${yui_name}-min.js") %] 121 | [% END %] 122 | [% starting_js_urls.push('js/global.js') %] 123 | 124 | [% FOREACH asset_url = concatenate_js(starting_js_urls) %] 125 | [% PROCESS format_js_link %] 126 | [% END %] 127 | 128 | 193 | 194 | [% FOREACH asset_url = concatenate_js(javascript_urls) %] 195 | [% PROCESS format_js_link %] 196 | [% END %] 197 | 198 | [%# this puts the live bookmark up on firefox for the Atom feed %] 199 | [% IF atomlink %] 200 | 203 | [% END %] 204 | 205 | [%# Required for the 'Autodiscovery' feature in Firefox 2 and IE 7. %] 206 | 208 | 209 | [% Hook.process("additional_header") %] 210 | 211 | 212 | 217 | 218 | 255 | 256 |
    257 | [% IF Param('announcehtml') %] 258 | [% Param('announcehtml') FILTER none %] 259 | [% END %] 260 | 261 | [% IF message %] 262 |
    [% message %]
    263 | [% END %] 264 | 265 | [% BLOCK format_css_link %] 266 | 267 | [% END %] 268 | 269 | [% BLOCK format_js_link %] 270 | 271 | [% END %] 272 | -------------------------------------------------------------------------------- /template/en/custom/global/textarea.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [%# INTERFACE: 10 | # 11 | # id: (optional) The "id"-attribute of the textarea. 12 | # name: (optional) The "name"-attribute of the textarea. 13 | # accesskey: (optional) The "accesskey"-attribute of the textarea. 14 | # classes: (optional) The "class"-attribute of the textarea. 15 | # wrap: (deprecated; optional) The "wrap"-attribute of the textarea. 16 | # disabled: (optional) Disable the textarea. 17 | # readonly: (optional) Prevent the textarea from being edited. 18 | # minrows: (required) Number of rows the textarea shall have initially 19 | # and when not having focus. 20 | # maxrows: (optional) Number of rows the textarea shall have if 21 | # maximized (which happens on getting focus). If not given, 22 | # the textarea doesn't maximize when getting focus. 23 | # defaultrows: (optional) Number of rows the textarea shall have if 24 | # the zoom_textareas user preference if off. If not given, 25 | # minrows will be used. 26 | # cols: (required) Number of columns the textarea shall have. 27 | # defaultcontent: (optional) Default content for the textarea. 28 | # mandatory: (optional) Boolean specifying whether or not the textarea 29 | # is mandatory. 30 | #%] 31 | 32 | 54 | [% IF id == 'comment' %] 55 |
    56 | 62 |
    63 | 64 | Quick Editing Help 65 | 66 |
    68 |

    To link to other bugs in the description or comment, type 69 | bug #NUMBER, e.g. bug #17576.

    70 |

    To link to another comment within the current bug, type 71 | comment #NUMBER, e.g. comment #3.

    72 |

    To link to a comment within another bug, type 73 | bug #NUMBER, comment #NUMBER, e.g. 74 | bug #17576, comment #2.

    75 |

    To link to a SVN commit, use base rNUMBER for the 76 | base system, ports rNUMBER for ports and 77 | doc rNUMBER for documentation commits, e.g. 78 | base r33858.

    79 |

    To link to a Diff on 80 | reviews.FreeBSD.org, use review DNUMBER, e.g. 81 | review D123.

    82 |
    83 |
    84 | [% END %] 85 | 86 | -------------------------------------------------------------------------------- /template/en/custom/global/variables.none.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [%# This is a list of terms that may be changed to "brand" the Bugzilla 10 | # instance (for example, referring to "bugs" as "issues".) When used, these 11 | # strings are used in several different types of content, and are not 12 | # protected with Template-Toolkit FILTERs. Consequently, no special 13 | # characters are allowed. 14 | # 15 | # Remember to PROCESS rather than INCLUDE this template. 16 | #%] 17 | 18 | [% terms = { 19 | "bug" => "bug", 20 | "Bug" => "Bug", 21 | "abug" => "a bug", 22 | "Abug" => "A bug", 23 | "aBug" => "a Bug", 24 | "ABug" => "A Bug", 25 | "bugs" => "bugs", 26 | "Bugs" => "Bugs", 27 | "comment" => "comment", 28 | "comments" => "comments", 29 | "zeroSearchResults" => "Zero bugs found", 30 | "Bugzilla" => "FreeBSD Bugzilla" 31 | } 32 | %] 33 | 34 | [% USE Hook %] 35 | [% Hook.process("end") %] 36 | -------------------------------------------------------------------------------- /template/en/custom/hook/global/user-error-errors.html.tmpl: -------------------------------------------------------------------------------- 1 | [% IF error == "password_change_requests_use_kpasswd" %] 2 | [% title = "Use kpasswd to reset password" %] 3 | Use FreeBSD Cluster's Kerberos system to reset password. 4 | For detailed instruction 5 | follow this link. 6 | [% END %] 7 | -------------------------------------------------------------------------------- /template/en/custom/index.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [%# INTERFACE: 10 | # release: a hash containing data about new releases, if any. 11 | #%] 12 | 13 | [% PROCESS global/header.html.tmpl 14 | title = "$terms.Bugzilla Main Page" 15 | header = "Main Page" 16 | style_urls = [ 'skins/standard/index.css' ] 17 | %] 18 | 19 | [% IF release %] 20 |
    21 | [% IF release.data %] 22 | [% IF release.eos_date %] 23 |

    [% terms.Bugzilla %] [%+ release.branch_version FILTER html %] will 24 | no longer receive security updates after [% release.eos_date FILTER html %]. 25 | You are highly encouraged to upgrade in order to keep your 26 | system secure.

    27 | [% END %] 28 | [% IF release.deprecated %] 29 |

    Bugzilla [%+ release.deprecated FILTER html %] is no longer 30 | supported. You are highly encouraged to upgrade in order to keep your 31 | system secure.

    32 | [% END %] 33 | 34 |

    A new Bugzilla version ([% release.data.latest_ver FILTER html %]) 35 | is available at 36 | [% release.data.url FILTER html %].
    37 | Release date: [% release.data.date FILTER html %]

    38 | 39 |

    This message is only shown to logged in users with admin privs. 40 | You can configure this notification from the 41 | Parameters page.

    42 | [% ELSIF release.error == "cannot_download" %] 43 |

    The remote file 44 | [%~ constants.REMOTE_FILE FILTER html %] cannot be downloaded 45 | (reason: [% release.reason FILTER html %]).
    46 | Either the remote server is temporarily unavailable, or your web server cannot access 47 | the web. If you are behind a proxy, set the 48 | proxy_url parameter correctly.

    49 | [% ELSIF release.error == "no_write" %] 50 |

    The local XML file '[% constants.LOCAL_FILE FILTER html %]' cannot be created 51 | (reason: [% release.reason FILTER html %]).
    52 | Please make sure the web server can write into this directory. 53 | [% ELSIF release.error == "no_update" %] 54 |

    The local XML file '[% constants.LOCAL_FILE FILTER html %]' cannot be updated. 55 | Please make sure the web server can edit this file.

    56 | [% ELSIF release.error == "no_access" %] 57 |

    The local XML file '[% constants.LOCAL_FILE FILTER html %]' cannot be read. 58 | Please make sure this file has the correct rights set on it.

    59 | [% ELSIF release.error == "corrupted" %] 60 |

    The local XML file '[% constants.LOCAL_FILE FILTER html %]' has an invalid XML format. 61 | Please delete it and try accessing this page again.

    62 | [% ELSIF release.error == "unknown_parameter" %] 63 |

    '[% Param("upgrade_notification") FILTER html %]' is not a valid notification 64 | parameter. Please check this parameter in the 65 | Parameters page.

    66 | [% END %] 67 |
    68 | [% END %] 69 | 70 |
    71 |
    72 |

    Welcome to [% terms.Bugzilla %]

    73 |
    [% Hook.process('intro') %]
    74 |
    75 |

    76 | Use these quick links to report a problem or update: 77 |

    78 | 89 | 90 |

    Use the full bug report page to report other issues:

    91 | 96 |
    97 | 98 |
    99 |

    Or see if the issue has been reported already:

    100 |
    101 | 112 |
    113 | 116 |
    117 | 118 |
    119 | 152 |
    153 |
    154 | 155 | [% IF user.login %] 156 |
    157 |

    Latest [% terms.Bugzilla %] Changes

    158 |
    159 | [% INCLUDE news.html.tmpl %] 160 |
    161 | [% END %] 162 |
    163 | 164 |
    [% Hook.process('outro') %]
    165 |
    166 | 167 | [% PROCESS global/footer.html.tmpl %] 168 | -------------------------------------------------------------------------------- /template/en/custom/list/table.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [%############################################################################%] 10 | [%# Initialization #%] 11 | [%############################################################################%] 12 | 13 | [%# Don't display the table or do any processing if there are no bugs 14 | # to display %] 15 | [% RETURN IF !bugs.size %] 16 | 17 | [%# Columns whose titles or values should be abbreviated to make the list 18 | # more compact. For columns whose titles should be abbreviated, 19 | # the shortened title is included. For columns whose values should be 20 | # abbreviated, a maximum length is provided along with the ellipsis that 21 | # should be added to an abbreviated value, if any. 22 | # wrap is set if a column's contents should be allowed to be word-wrapped 23 | # by the browser. 24 | #%] 25 | 26 | [% field_descs.short_short_desc = field_descs.short_desc %] 27 | [% field_descs.assigned_to_realname = field_descs.assigned_to %] 28 | [% field_descs.reporter_realname = field_descs.reporter %] 29 | [% field_descs.qa_contact_realname = field_descs.qa_contact %] 30 | 31 | [%# Setting maxlength => 0 means no limit. We set it for performance reasons. %] 32 | [% abbrev = 33 | { 34 | "bug_severity" => { maxlength => 3 , title => "Sev" } , 35 | "priority" => { maxlength => 7 , title => "Pri" } , 36 | "rep_platform" => { maxlength => 3 , title => "HW" } , 37 | "bug_status" => { maxlength => 12 } , 38 | "assigned_to" => { maxlength => 30 , ellipsis => "..." } , 39 | "assigned_to_realname" => { maxlength => 20 , ellipsis => "..." } , 40 | "reporter" => { maxlength => 30 , ellipsis => "..." } , 41 | "reporter_realname" => { maxlength => 20 , ellipsis => "..." } , 42 | "qa_contact" => { maxlength => 30 , ellipsis => "..." , title => "QAContact" } , 43 | "qa_contact_realname" => { maxlength => 20 , ellipsis => "..." , title => "QAContact" } , 44 | "resolution" => { maxlength => 15 } , 45 | "short_desc" => { maxlength => 0, wrap => 1 } , 46 | "short_short_desc" => { maxlength => 60 , ellipsis => "..." , wrap => 1 } , 47 | "status_whiteboard" => { maxlength => 0, title => "Whiteboard" , wrap => 1 } , 48 | "keywords" => { maxlength => 0, wrap => 1 } , 49 | "tags" => { maxlength => 0, title => "Tags", wrap => 1}, 50 | "dependson" => { maxlength => 0, wrap => 1 } , 51 | "blocked" => { maxlength => 0, wrap => 1 } , 52 | "flagtypes.name" => { maxlength => 0, wrap => 1 } , 53 | "component" => { maxlength => 18 , title => "Component" } , 54 | "product" => { maxlength => 18 } , 55 | "version" => { maxlength => 5 , title => "Vers" } , 56 | "op_sys" => { maxlength => 12 } , 57 | "bug_file_loc" => { maxlength => 30 } , 58 | "target_milestone" => { maxlength => 0, title => "TargetM" } , 59 | "longdescs.count" => { maxlength => 0, title => "# Comments" }, 60 | "percentage_complete" => { maxlength => 0, format_value => "%d %%" } , 61 | } 62 | %] 63 | 64 | [% PROCESS bug/time.html.tmpl %] 65 | 66 | [% Hook.process("before_table") %] 67 | 68 | [%############################################################################%] 69 | [%# Table Header #%] 70 | [%############################################################################%] 71 | 72 | [% tableheader = BLOCK %] 73 | 74 | 75 | [% IF dotweak %] 76 | 77 | [% END %] 78 | 87 | 88 | [% IF splitheader %] 89 | 90 | [% FOREACH id = displaycolumns %] 91 | [% NEXT UNLESS loop.count() % 2 == 0 %] 92 | [% column = columns.$id %] 93 | [% PROCESS columnheader %] 94 | [% END %] 95 | 96 | 97 | [% IF dotweak %] 98 | 99 | [% END %] 100 | 101 | 102 | [% FOREACH id = displaycolumns %] 103 | [% NEXT IF loop.count() % 2 == 0 %] 104 | [% column = columns.$id %] 105 | [% PROCESS columnheader %] 106 | [% END %] 107 | 108 | [% ELSE %] 109 | 110 | [% FOREACH id = displaycolumns %] 111 | [% column = columns.$id %] 112 | [% PROCESS columnheader %] 113 | [% END %] 114 | 115 | [% END %] 116 | 117 | 118 | [% END %] 119 | 120 | [% BLOCK columnheader %] 121 | 130 | [% END %] 131 | 132 | [% BLOCK new_order %] 133 | [% desc = '' %] 134 | [% IF (om = order.match("\\b$id( DESC)?")) %] 135 | [% desc = ' DESC' IF NOT om.0 %] 136 | [% END %] 137 | [% id _ desc FILTER uri %] 138 | [% IF id != 'bug_id' AND order %] 139 | [% ',' _ order.remove("\\b$id( DESC)?(,\\s*|\$)") FILTER uri %] 140 | [% END %] 141 | [% END %] 142 | 143 | [% BLOCK order_arrow %] 144 | [% IF order.search("^$id DESC") %] 145 | 146 | [% ELSIF order.search("^$id(,\\s*|\$)") %] 147 | 148 | [% ELSIF order.search("\\b$id DESC") %] 149 | 150 | [% ELSIF order.search("\\b$id(,\\s*|\$)") %] 151 | 152 | [% END %] 153 | [% END %] 154 | 155 | [%############################################################################%] 156 | [%# Bug Table #%] 157 | [%############################################################################%] 158 | 159 | [% tableheader %] 160 | 161 | [% FOREACH bug = bugs %] 162 | [% count = loop.count() %] 163 | 164 | 173 | 174 | [% IF dotweak %] 175 | 178 | [% END %] 179 | 183 | 184 | [% FOREACH column = displaycolumns %] 185 | [% col_abbrev = abbrev.$column %] 186 | 226 | [% END %] 227 | 228 | 229 | [% END %] 230 | 231 | [% IF time_info.time_present %] 232 | [% PROCESS time_summary_line %] 233 | [% END %] 234 | 235 |
      79 | ID 84 | [% PROCESS order_arrow id='bug_id' ~%] 85 | 86 |
      
    122 | 126 | [%- abbrev.$id.title || field_descs.$id || column.title FILTER html -%] 127 | [% PROCESS order_arrow ~%] 128 | 129 |
    176 | 177 | 180 | [% bug.bug_id %] 181 | [%+ '[SEC]' IF bug.secure_mode %] 182 | 190 | [% IF col_abbrev.maxlength %] 191 | 192 | [% END %] 193 | [% IF col_abbrev.format_value %] 194 | [%- bug.$column FILTER format(col_abbrev.format_value) FILTER html -%] 195 | [% ELSIF column == 'actual_time' || 196 | column == 'remaining_time' || 197 | column == 'estimated_time' %] 198 | [% PROCESS formattimeunit time_unit=bug.$column %] 199 | [%# Display the login name of the user if their real name is empty. %] 200 | [% ELSIF column.search('_realname$') && bug.$column == '' %] 201 | [% SET login_column = column.remove('_realname$') %] 202 | [% bug.${login_column}.truncate(col_abbrev.maxlength, 203 | col_abbrev.ellipsis) FILTER html %] 204 | [% ELSIF column == 'short_desc' || column == "short_short_desc" %] 205 | 206 | [%- bug.$column.truncate(col_abbrev.maxlength, col_abbrev.ellipsis) FILTER html -%] 207 | 208 | [% ELSIF bug_fields.$column.type == constants.FIELD_TYPE_BUG_ID %] 209 | 210 | [%- bug.$column.truncate(col_abbrev.maxlength, col_abbrev.ellipsis) FILTER html -%] 211 | 212 | [% ELSIF bug_fields.$column.type == constants.FIELD_TYPE_TEXTAREA %] 213 | [%- bug.$column.truncate(256, '...') FILTER html -%] 214 | [% ELSIF column == 'bug_file_loc' && is_safe_url(bug.bug_file_loc) %] 215 | 217 | [%- display_value(column, bug.$column).truncate(col_abbrev.maxlength, col_abbrev.ellipsis) FILTER html -%] 218 | 219 | [% ELSE %] 220 | [%- display_value(column, bug.$column).truncate(col_abbrev.maxlength, col_abbrev.ellipsis) FILTER html -%] 221 | [% END %] 222 | [% IF col_abbrev.maxlength %] 223 | 224 | [% END %] 225 |
    236 | 237 | [% BLOCK time_summary_line %] 238 | 239 | [% columns_to_span = 1 %] [%# bugID %] 240 | [% IF dotweak %] 241 | [% columns_to_span = columns_to_span + 1 %] 242 | [% END %] 243 | [% FOREACH column = displaycolumns %] 244 | [% IF column == 'actual_time' || 245 | column == 'remaining_time' || 246 | column == 'estimated_time' || 247 | column == 'percentage_complete' %] 248 | [% IF columns_to_span > 0 %] 249 | Totals 251 | [% columns_to_span = 0 %] 252 | [% END %] 253 | [% IF column == 'percentage_complete' %] 254 | [% time_info.percentage_complete 255 | FILTER format(abbrev.$column.format_value) FILTER html %] 256 | [% ELSE %] 257 | 258 | [%- PROCESS formattimeunit time_unit=time_info.$column %] 259 | [% END %] 260 | [% ELSIF columns_to_span == 0 %] [%# A column following the first total %] 261 |   262 | [% ELSE %] [%# We haven't gotten to a time column yet, keep computing span %] 263 | [% columns_to_span = columns_to_span + 1 %] 264 | [% END %] 265 | [% END %] 266 | 267 | [% END %] 268 | -------------------------------------------------------------------------------- /template/en/custom/news.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# News file for the FreeBSD bugzilla instance #%] 2 | [% news = [ 3 | { 4 | date => '2025-01-04', 5 | text => '
      6 |
    • Bugzilla is in the process of being updated to 5.0.4.1. This 7 | is a security and minor bugfix update.
    • 8 |
    ' 9 | }, 10 | { 11 | date => '2024-11-23', 12 | text => '
      13 |
    • Version 14.2-RELEASE of product Base System was created.
    • 14 |
    ' 15 | }, 16 | { 17 | date => '2024-11-03', 18 | text => '
      19 |
    • Version 14.2-STABLE of product Base System was created.
    • 20 |
    ' 21 | }, 22 | { 23 | date => '2024-08-02', 24 | text => '
      25 |
    • Version 13.4-STABLE of product Base System was created.
    • 26 |
    ' 27 | }, 28 | { 29 | date => '2024-07-01', 30 | text => '
      31 |
    • As support for them have ended, Base System versions 13.2-* were 32 | disabled for new bugs.
    • 33 |
    • Belatedly, version 13.3-STABLE of product Base System was created.
    • 34 |
    ' 35 | }, 36 | { 37 | date => '2024-06-15', 38 | text => '
      39 |
    • Versions 14.1-RELEASE and 14.1-STABLE of product Base System were 40 | created.
    • 41 |
    • As an experiment, the keyword "vendor" has been created. Set this 42 | to flag a PR as filed from an upstream or downstream of FreeBSD.
    • 43 |
    ' 44 | }, 45 | { 46 | date => '2024-01-20', 47 | text => '
      48 |
    • As an experiment, the flag "needs-errata" has been created. Set this 49 | to ? to ask Release Engineering or Secteam to evaluate whether an 50 | Errata Notice 51 | should be created for an existing release.
    • 52 |
    ' 53 | }, 54 | { 55 | date => '2024-01-04', 56 | text => '
      57 |
    • As support for it has ended, Base System version 12.4 was disabled 58 | for new bugs.
    • 59 |
    • The weekly reminder script was updated to deselect mfc-stable12.
    • 60 |
    • The Dashboard MFC query was similarly updated.
    • 61 |
    ' 62 | }, 63 | { 64 | date => '2023-12-17', 65 | text => '
      66 |
    • The weekly reminder script was belatedly updated to: 67 |
        68 |
      • deselect mfc-stable8 through 11.
      • 69 |
      • select mfc-stable12 through 14.
      • 70 |
      71 |
    • The Dashboard MFC query was belatedly fixed as above.
    • 72 |
    • Note that mfc-stable12 will be removed 20240101.
    • 73 |
    ' 74 | }, 75 | { 76 | date => '2023-11-30', 77 | text => '
      78 |
    • A stale query was removed from the Dashboard (PR 215844).
    • 79 |
    • max_search_results was updated from 10000 to 12500. 80 | This was limiting the totals from certain queries.
    • 81 |
    ' 82 | }, 83 | { 84 | date => '2023-08-30', 85 | text => '
      86 |
    • To reflect the prelease state of FreeBSD 14.0, the following were 87 | done: 88 |
        89 |
      • Version 14.0-STABLE of product Base System was created.
      • 90 |
      • Version 15.0-CURRENT of product Base System was created.
      • 91 |
      • The flag type mfc-stable14 was created.
      • 92 |
      93 |
    • As support for them has ended, Base System versions 12.0-*, 12.1-*, 94 | 12.2-*, 12.3-*, 13.0-*, and 13.1-* were disabled for new bugs.
    • 95 |
    ' 96 | }, 97 | { 98 | date => '2021-01-24', 99 | text => '
      100 |
    • To reflect the prelease state of FreeBSD 13.0, the following were 101 | done: 102 |
        103 |
      • Version 13.0-STABLE of product Base System was created.
      • 104 |
      • The flag type mfc-stable13 was created.
      • 105 |
      106 |
    • As support for them has ended, Base System versions 12.0-RELEASE 107 | and 12.0-STABLE were disabled for new bugs.
    • 108 |
    • Hardware value sparc64 has been disabled for new bugs.
    • 109 |
    ' 110 | }, 111 | { 112 | date => '2020-07-12', 113 | text => '
      114 |
    • In order to lay the groundwork for future changes/improvements, 115 | and to make it easier to search for things that are not assigned, 116 | we have modified the Real Name of all bugzilla accounts 117 | that are default assignees as such:
      118 |     Assignee: freebsd-net (Nobody).
      119 | This is to make it more obvious that default assignees are assigned to 120 | Nobody and encourages self-assignment (to real people) to 121 | make resolution more consistent.
    • 122 |
    ' 123 | }, 124 | { 125 | date => '2020-07-11', 126 | text => '
      127 |
    • To help support the 202007 Bug Squash, all of the PRs containing 128 | patches were audited so that: 129 |
        130 |
      • Each attachment that has its Patch flag set has 131 | been verified to contain a patch.
      • 132 |
      • If an attachment filename ends in .diff or 133 | .patch, its Patch flag has been 134 | verified to be set.
      • 135 |
      • The remaining attachment filenames end in .shar.
      • 136 |
      137 |
    • 138 |
    • This officially obsoletes the use of the the Keywords 139 | field value of patch. This value had to be added by hand 140 | and as a result was applied inconsistently.
    • 141 |
    ' 142 | }, 143 | ] 144 | %] 145 | 146 | [% FOREACH item IN news %] 147 | [% BREAK IF loop.count() > 5 %] 148 |
    149 | [% item.date FILTER html %] 150 |

    [% item.text %]

    151 |
    152 | [% END %] 153 | -------------------------------------------------------------------------------- /template/en/custom/pages/fields.html.tmpl: -------------------------------------------------------------------------------- 1 | [%# This Source Code Form is subject to the terms of the Mozilla Public 2 | # License, v. 2.0. If a copy of the MPL was not distributed with this 3 | # file, You can obtain one at http://mozilla.org/MPL/2.0/. 4 | # 5 | # This Source Code Form is "Incompatible With Secondary Licenses", as 6 | # defined by the Mozilla Public License, v. 2.0. 7 | #%] 8 | 9 | [% PROCESS global/header.html.tmpl 10 | title = "$terms.Bug Fields" 11 | bodyclasses = ['narrow_page'] 12 | %] 13 | 14 |

    This page describes the various fields that you see 15 | on [% terms.abug %].

    16 | 17 | 18 | 19 | 20 | 23 | 24 | 27 | 28 | 29 | 30 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 85 | 86 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 121 | 122 | 171 | 172 | 173 |
    21 |

    [% field_descs.bug_status FILTER upper FILTER html %]

    22 |
    25 |

    [% field_descs.resolution FILTER upper FILTER html %]

    26 |
    The [% field_descs.bug_status FILTER html %] field indicates the 31 | current state of a [% terms.bug %]. Only certain status transitions 32 | are allowed.The [% field_descs.resolution FILTER html %] field indicates what 35 | happened to this [%+ terms.bug %].
    Open [% terms.Bugs %]
    45 |
    46 |
    47 | [% display_value("bug_status", "UNCONFIRMED") FILTER html %] 48 |
    49 |
    50 | This [% terms.bug %] has recently been added to the database. 51 | Nobody has confirmed that this [% terms.bug %] is valid. Users 52 | who have the "canconfirm" permission set may confirm 53 | this [% terms.bug %], changing its state to 54 | [% display_value("bug_status", "CONFIRMED") FILTER html %]. 55 | Or, it may be directly resolved and marked 56 | [% display_value("bug_status", "RESOLVED") FILTER html %]. 57 |
    58 | 59 |
    60 | [% display_value("bug_status", "CONFIRMED") FILTER html %] 61 |
    62 |
    63 | This [% terms.bug %] is valid and has recently been filed. 64 | [%+ terms.Bugs %] in this state become 65 | [% display_value("bug_status", "IN_PROGRESS") FILTER html %] 66 | when somebody is working on them, or become resolved and marked 67 | [% display_value("bug_status", "RESOLVED") FILTER html %]. 68 |
    69 | 70 |
    71 | [% display_value("bug_status", "IN_PROGRESS") FILTER html %] 72 |
    73 |
    74 | This [% terms.bug %] is not yet resolved, but is assigned to the 75 | proper person who is working on the [% terms.bug %]. From here, 76 | [%+ terms.bugs %] can be given to another person and become 77 | [% display_value("bug_status", "CONFIRMED") FILTER html %], or 78 | resolved and become 79 | [% display_value("bug_status", "RESOLVED") FILTER html %]. 80 |
    81 | 82 | [% Hook.process('open-status') %] 83 |
    84 |
    87 | No resolution yet. All [% terms.bugs %] which are in one of 88 | these "open" states have no resolution set. 89 |
    Closed [% terms.Bugs %]
    98 |
    99 |
    100 | [% display_value("bug_status", "RESOLVED") FILTER html %] 101 |
    102 |
    103 | A resolution has been performed, and it is awaiting verification by 104 | QA. From here [% terms.bugs %] are either reopened and given some 105 | open status, or are verified by QA and marked 106 | [% display_value("bug_status", "VERIFIED") FILTER html %]. 107 |
    108 | 109 |
    110 | [% display_value("bug_status", "VERIFIED") FILTER html %] 111 |
    112 |
    113 | QA has looked at the [% terms.bug %] and the resolution and 114 | agrees that the appropriate resolution has been taken. This is 115 | the final status for [% terms.bugs %]. 116 |
    117 | 118 | [% Hook.process('closed-status') %] 119 |
    120 |
    123 |
    124 |
    125 | [% display_value("resolution", "FIXED") FILTER html %] 126 |
    127 |
    128 | A fix for this [% terms.bug %] is checked into the tree and 129 | tested. 130 |
    131 | 132 |
    133 | [% display_value("resolution", "INVALID") FILTER html %] 134 |
    135 |
    136 | The problem described is not [% terms.abug %]. 137 |
    138 | 139 |
    140 | [% display_value("resolution", "WONTFIX") FILTER html %] 141 |
    142 |
    143 | The problem described is [% terms.abug %] which will never be 144 | fixed. 145 |
    146 | 147 |
    148 | [% display_value("resolution", "DUPLICATE") FILTER html %] 149 |
    150 |
    151 | The problem is a duplicate of an existing [% terms.bug %]. 152 | When [% terms.abug %] is marked as a 153 | [% display_value("resolution", "DUPLICATE") FILTER html %], 154 | you will see which [% terms.bug %] it is a duplicate of, 155 | next to the resolution. 156 |
    157 | 158 |
    159 | [% display_value("resolution", "WORKSFORME") FILTER html %] 160 |
    161 |
    162 | All attempts at reproducing this [% terms.bug %] were futile, 163 | and reading the code produces no clues as to why the described 164 | behavior would occur. If more information appears later, 165 | the [% terms.bug %] can be reopened. 166 |
    167 | 168 | [% Hook.process('resolution') %] 169 |
    170 |
    174 | 175 |

    Other Fields

    176 | 177 | [% SET field_help_map = {} %] 178 | [% FOREACH field = bug_fields.keys %] 179 | [% SET field_desc = field_descs.$field %] 180 | [% field_help_map.$field_desc = { help => help_html.$field, 181 | field => field } %] 182 | [% END %] 183 | 184 | [%# This field is not a real one, but its label is visible in bugs. %] 185 | 186 | [% field_help_map.Importance = { help => help_html.importance, 187 | field => "importance" } %] 188 | 189 | [%# These are fields that don't need to be documented, either because 190 | # they have docs somewhere else in the UI, or they don't show up on bugs. 191 | # %] 192 | [% SET skip_fields = [ 193 | 'days_elapsed', 194 | 'everconfirmed', 195 | 'reporter_accessible', 196 | 'cclist_accessible', 197 | 'bug_group', 198 | 'commenter', 199 | 'owner_idle_time', 200 | 'bug_status', 201 | 'resolution', 202 | ] %] 203 | 204 |
    205 | [% FOREACH field_desc = field_help_map.keys.sort %] 206 | [% SET field = field_help_map.${field_desc}.field %] 207 | [% SET field_object = bug_fields.$field %] 208 | 209 | [% NEXT IF field_object.obsolete %] 210 | [% NEXT IF !user.is_timetracker AND field_object.is_timetracking %] 211 | 212 | [% NEXT IF field == 'status_whiteboard' AND !Param('usestatuswhiteboard') %] 213 | [% NEXT IF field == 'target_milestone' AND !Param('usetargetmilestone') %] 214 | 215 | [%# For now we don't have help for attachment fields and so on. %] 216 | [% NEXT IF field.match('\.') %] 217 | 218 | [% NEXT IF skip_fields.contains(field) %] 219 | 220 |
    [% field_desc FILTER html %]
    221 |
    222 | [% field_help_map.${field_desc}.help FILTER html_light %] 223 |
    224 | [% END %] 225 |
    226 | 227 | [% PROCESS global/footer.html.tmpl %] 228 | -------------------------------------------------------------------------------- /weeklyreminder.pl: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/perl -w 2 | 3 | use strict; 4 | use warnings; 5 | use lib qw(. lib); 6 | 7 | use Bugzilla; 8 | use Bugzilla::Mailer; 9 | use Bugzilla::Constants; 10 | use Bugzilla::Util; 11 | use Email::MIME; 12 | 13 | use constant { 14 | # Consider bugs, for which nothing happened for more than X days 15 | WAIT => 7 16 | }; 17 | 18 | my $urlbase = Bugzilla->params->{'urlbase'}; 19 | 20 | my $MAIL_TXT_HEADER = "To view an individual PR, use: 21 | ${urlbase}show_bug.cgi?id=(Bug Id). 22 | "; 23 | 24 | my $MAIL_HTML_HEADER = '
    ';
     25 | 
     26 | my $MAIL_COMMON = "
     27 | The following is a listing of current problems submitted by FreeBSD users,
     28 | which need special attention. These represent problem reports covering
     29 | all versions including experimental development code and obsolete releases.
     30 | 
     31 | Status      |    Bug Id | Description
     32 | ------------+-----------+---------------------------------------------------
     33 | %s
     34 | %d problems total for which you should take action.
     35 | ";
     36 | 
     37 | my $MAIL_TXT_FOOTER = "";
     38 | 
     39 | my $MAIL_HTML_FOOTER = '
    '; 40 | 41 | my $TBLROW = "%-11s | %9s | %-50.49s\n"; 42 | my $TBLROW_HTML = "%-11s | %9s | %s\n"; 43 | 44 | # We're a non-interactive user 45 | Bugzilla->usage_mode(USAGE_MODE_CMDLINE); 46 | # Get the db conection and query the db directly. 47 | my $dbh = Bugzilla->dbh; 48 | 49 | my $dnow = $dbh->sql_to_days("NOW()"); 50 | my $mfcquery = q{ 51 | SELECT DISTINCT 52 | bugs.bug_id, bugs.short_desc, bugs.bug_status, p.login_name, bugs.assigned_to 53 | FROM 54 | bugs 55 | JOIN bug_status bs ON (bs.value = bugs.bug_status AND bs.is_open != 0) 56 | JOIN flags ON (bugs.bug_id = flags.bug_id AND flags.status = '?') 57 | JOIN flagtypes ON (flags.type_id = flagtypes.id) 58 | JOIN profiles p ON (p.userid = bugs.assigned_to AND p.is_enabled = 1) 59 | WHERE 60 | flagtypes.name IN (?, ?, ?) 61 | AND } . $dnow . " - " . $dbh->sql_to_days("bugs.delta_ts") . " >= " . WAIT . 62 | " ORDER BY bugs.assigned_to, bugs.bug_status, bugs.bug_id;"; 63 | 64 | my $openbugs = $dbh->selectall_arrayref( 65 | $mfcquery, 66 | undef, 67 | 'merge-quarterly', 68 | 'mfc-stable13', 69 | 'mfc-stable14'); 70 | 71 | my $kwdquery = q{ 72 | SELECT DISTINCT 73 | bugs.bug_id, bugs.short_desc, bugs.bug_status, p.login_name, bugs.assigned_to 74 | FROM 75 | bugs 76 | JOIN bug_status bs ON (bs.value = bugs.bug_status AND bs.is_open != 0) 77 | JOIN keywords ON (keywords.bug_id = bugs.bug_id) 78 | JOIN keyworddefs ON (keywords.keywordid = keyworddefs.id) 79 | JOIN profiles p ON (p.userid = bugs.assigned_to AND p.is_enabled = 1) 80 | WHERE 81 | keyworddefs.name IN (?) 82 | AND } . $dnow . " - " . $dbh->sql_to_days("bugs.delta_ts") . " >= " . WAIT . 83 | " ORDER BY bugs.assigned_to, bugs.bug_status, bugs.bug_id;"; 84 | 85 | my $kwdbugs = $dbh->selectall_arrayref( 86 | $kwdquery, 87 | undef, 88 | 'patch-ready'); 89 | push(@$openbugs, @$kwdbugs); 90 | 91 | 92 | my $reqquery = " 93 | SELECT DISTINCT 94 | bugs.bug_id, bugs.short_desc, bugs.bug_status, p.login_name, bugs.assigned_to 95 | FROM 96 | bugs 97 | JOIN flags ON (bugs.bug_id = flags.bug_id AND flags.status = '?' AND flags.requestee_id IS NOT NULL) 98 | JOIN bug_status bs ON (bs.value = bugs.bug_status AND bs.is_open != 0) 99 | JOIN profiles p ON (p.userid = flags.requestee_id) 100 | WHERE 101 | bugs.assigned_to != flags.requestee_id"; 102 | 103 | my $reqbugs = $dbh->selectall_arrayref($reqquery, undef); 104 | push(@$openbugs, @$reqbugs); 105 | 106 | # If there are no bugs (hah!), exit gracefully 107 | if (scalar(@$openbugs) == 0) { 108 | exit 0; 109 | } 110 | 111 | # Create a hash table based on the assigned logins (email): 112 | # bugs{email} = [list of bugs]. 113 | my %bugs; 114 | foreach my $bug (@$openbugs) { 115 | my ($id, $desc, $status, $mail) = @$bug; 116 | if (!defined($bugs{$mail})) { 117 | $bugs{$mail} = []; 118 | } 119 | push(@{$bugs{$mail}}, $bug); 120 | } 121 | 122 | foreach my $mail (keys %bugs) { 123 | # Prep mail content 124 | my $tblbugs = ""; 125 | my $tblbugs_html = ""; 126 | my $bugcount = scalar(@{$bugs{$mail}}); 127 | foreach my $bug (@{$bugs{$mail}}) { 128 | my ($id, $desc, $status, $mail__, @assignedto) = @$bug; 129 | $tblbugs .= sprintf($TBLROW, $status, $id, $desc); 130 | # we can not use %50s format on HTML-escaped string 131 | # sut it implicitly 132 | my $desc_cut = html_quote(substr($desc, 0, 49)); 133 | my $url = "${urlbase}show_bug.cgi?id=$id"; 134 | $tblbugs_html .= sprintf($TBLROW_HTML, $status, $url, $id, $url, $desc_cut); 135 | $tblbugs_html =~ s/()(\s+)/$2$1/; 136 | } 137 | 138 | my $body_txt = $MAIL_TXT_HEADER; 139 | $body_txt .= sprintf($MAIL_COMMON, $tblbugs, $bugcount); 140 | $body_txt .= $MAIL_TXT_FOOTER; 141 | 142 | my $body_html = $MAIL_HTML_HEADER; 143 | $body_html .= sprintf($MAIL_COMMON, $tblbugs_html, $bugcount); 144 | $body_html .= $MAIL_HTML_FOOTER; 145 | 146 | # parts should go from least rich to most rich format 147 | my @parts = ( 148 | Email::MIME->create( 149 | attributes => { 150 | content_type => "text/plain", 151 | charset => "UTF-8" 152 | }, 153 | body => $body_txt 154 | ), 155 | Email::MIME->create( 156 | attributes => { 157 | content_type => "text/html", 158 | charset => "UTF-8" 159 | }, 160 | body => $body_html 161 | ), 162 | ); 163 | 164 | my $mailmsg = Email::MIME->create( 165 | header_str => [ 166 | From => 'bugzilla-noreply@FreeBSD.org', 167 | To => [ $mail ], 168 | Subject => "Problem reports for $mail that need special attention" 169 | ], 170 | attributes => { 171 | content_type => "multipart/alternative" 172 | }, 173 | parts => [ @parts ], 174 | ); 175 | 176 | # Send the mail via the bugzilla configuration. 177 | # print $mailmsg->as_string; 178 | MessageToMTA($mailmsg, 1); 179 | } 180 | --------------------------------------------------------------------------------