diff options
Diffstat (limited to 'Bugzilla')
-rw-r--r-- | Bugzilla/Attachment.pm | 47 | ||||
-rw-r--r-- | Bugzilla/Bug.pm | 43 | ||||
-rw-r--r-- | Bugzilla/BugMail.pm | 11 | ||||
-rw-r--r-- | Bugzilla/Component.pm | 6 | ||||
-rw-r--r-- | Bugzilla/Config/Advanced.pm | 6 | ||||
-rw-r--r-- | Bugzilla/Constants.pm | 26 | ||||
-rw-r--r-- | Bugzilla/Error.pm | 40 | ||||
-rw-r--r-- | Bugzilla/Field.pm | 167 | ||||
-rw-r--r-- | Bugzilla/Group.pm | 5 | ||||
-rw-r--r-- | Bugzilla/Install.pm | 2 | ||||
-rw-r--r-- | Bugzilla/Install/DB.pm | 5 | ||||
-rw-r--r-- | Bugzilla/Install/Filesystem.pm | 14 | ||||
-rw-r--r-- | Bugzilla/Product.pm | 5 | ||||
-rw-r--r-- | Bugzilla/Search.pm | 3 | ||||
-rw-r--r-- | Bugzilla/Search/Quicksearch.pm | 3 | ||||
-rw-r--r-- | Bugzilla/Template.pm | 25 | ||||
-rw-r--r-- | Bugzilla/Token.pm | 2 | ||||
-rw-r--r-- | Bugzilla/User.pm | 7 | ||||
-rw-r--r-- | Bugzilla/UserAgent.pm | 247 | ||||
-rw-r--r-- | Bugzilla/WebService/Bug.pm | 47 | ||||
-rw-r--r-- | Bugzilla/WebService/User.pm | 4 |
21 files changed, 617 insertions, 98 deletions
diff --git a/Bugzilla/Attachment.pm b/Bugzilla/Attachment.pm index 3a8e7d5d5..2a89678c5 100644 --- a/Bugzilla/Attachment.pm +++ b/Bugzilla/Attachment.pm @@ -414,6 +414,53 @@ sub datasize { return $self->{datasize}; } +=over + +=item C<linecount> + +the number of lines of the attachment content + +=back + +=cut + +# linecount allows for getting the number of lines of an attachment +# from the database directly if the data has not yet been loaded for +# performance reasons. + +sub linecount { + my ($self) = @_; + + return $self->{linecount} if exists $self->{linecount}; + + # Limit this to just text/* attachments as this could + # cause strange results for binary attachments. + return if $self->contenttype !~ /^text\//; + + # If the data has already been loaded, we can just determine + # line count from the data directly. + if ($self->{data}) { + $self->{linecount} = $self->{data} =~ tr/\n/\n/; + } + else { + $self->{linecount} = + int(Bugzilla->dbh->selectrow_array(" + SELECT LENGTH(attach_data.thedata)-LENGTH(REPLACE(attach_data.thedata,'\n',''))/LENGTH('\n') + FROM attach_data WHERE id = ?", undef, $self->id)); + + } + + # If we still do not have a linecount either the attachment + # is stored in a local file or has been deleted. If the former, + # we call self->data to force a load from the filesystem and + # then do a split on newlines and count again. + unless ($self->{linecount}) { + $self->{linecount} = $self->data =~ tr/\n/\n/; + } + + return $self->{linecount}; +} + sub _get_local_filename { my $self = shift; my $hash = ($self->id % 100) + 100; diff --git a/Bugzilla/Bug.pm b/Bugzilla/Bug.pm index 515fabb90..42a7f7f15 100644 --- a/Bugzilla/Bug.pm +++ b/Bugzilla/Bug.pm @@ -1628,6 +1628,14 @@ sub _check_groups { : $params->{product}; my %add_groups; + # BMO: Allow extension to add groups before the + # real checks are done. + Bugzilla::Hook::process('bug_check_groups', { + product => $product, + group_names => $group_names, + add_groups => \%add_groups + }); + # In email or WebServices, when the "groups" item actually # isn't specified, then just add the default groups. if (!defined $group_names) { @@ -1646,7 +1654,12 @@ sub _check_groups { foreach my $name (@$group_names) { my $group = Bugzilla::Group->check_no_disclose({ %args, name => $name }); - if (!$product->group_is_settable($group)) { + # BMO: Do not check group_is_settable if the group is + # already added, such as from the extension hook. group_is_settable + # will reject any group the user is not currently in. + if (!$add_groups{$group->id} + && !$product->group_is_settable($group)) + { ThrowUserError('group_restriction_not_allowed', { %args, name => $name }); } $add_groups{$group->id} = $group; @@ -1655,7 +1668,7 @@ sub _check_groups { # Now enforce mandatory groups. $add_groups{$_->id} = $_ foreach @{ $product->groups_mandatory }; - + my @add_groups = values %add_groups; return \@add_groups; } @@ -3246,15 +3259,37 @@ sub depends_on_obj { return $self->{depends_on_obj}; } +sub duplicates { + my $self = shift; + return $self->{duplicates} if exists $self->{duplicates}; + return [] if $self->{error}; + $self->{duplicates} = Bugzilla::Bug->new_from_list($self->duplicate_ids); + return $self->{duplicates}; +} + +sub duplicate_ids { + my $self = shift; + return $self->{duplicate_ids} if exists $self->{duplicate_ids}; + return [] if $self->{error}; + + my $dbh = Bugzilla->dbh; + $self->{duplicate_ids} = + $dbh->selectcol_arrayref('SELECT dupe FROM duplicates WHERE dupe_of = ?', + undef, $self->id); + return $self->{duplicate_ids}; +} + sub flag_types { - my ($self) = @_; + my ($self, $params) = @_; + $params ||= {}; return $self->{'flag_types'} if exists $self->{'flag_types'}; return [] if $self->{'error'}; my $vars = { target_type => 'bug', product_id => $self->{product_id}, component_id => $self->{component_id}, - bug_id => $self->bug_id }; + bug_id => $self->bug_id, + %$params }; $self->{'flag_types'} = Bugzilla::Flag->_flag_types($vars); return $self->{'flag_types'}; diff --git a/Bugzilla/BugMail.pm b/Bugzilla/BugMail.pm index 55eeeab25..60ff45e38 100644 --- a/Bugzilla/BugMail.pm +++ b/Bugzilla/BugMail.pm @@ -267,7 +267,16 @@ sub Send { } # Make sure the user isn't in the nomail list, and the dep check passed. - if ($user->email_enabled && $dep_ok) { + # BMO: normally we would check the login names of the + # user objects in the bugmail_recipients hook instead. There we could + # remove the (bugs|tld)$ addresses from the recipients. But the hook comes + # to early before watchers are decided and these addresses need to be + # there for this to work. This may change with recent enhancements to + # component watching. need to investigate further. + if ($user->email_enabled && $dep_ok && + ($user->login !~ /bugs$/) && + ($user->login !~ /\.tld$/)) + { # OK, OK, if we must. Email the user. $sent_mail = sendMail( { to => $user, diff --git a/Bugzilla/Component.pm b/Bugzilla/Component.pm index dc3cc1b9e..ad5166a0f 100644 --- a/Bugzilla/Component.pm +++ b/Bugzilla/Component.pm @@ -371,11 +371,13 @@ sub default_qa_contact { } sub flag_types { - my $self = shift; + my ($self, $params) = @_; + $params ||= {}; if (!defined $self->{'flag_types'}) { my $flagtypes = Bugzilla::FlagType::match({ product_id => $self->product_id, - component_id => $self->id }); + component_id => $self->id, + %$params }); $self->{'flag_types'} = {}; $self->{'flag_types'}->{'bug'} = diff --git a/Bugzilla/Config/Advanced.pm b/Bugzilla/Config/Advanced.pm index faab6bbbd..f5653ee86 100644 --- a/Bugzilla/Config/Advanced.pm +++ b/Bugzilla/Config/Advanced.pm @@ -62,6 +62,12 @@ use constant get_param_list => ( default => 'off', checker => \&check_multi }, + + { + name => 'disable_bug_updates', + type => 'b', + default => 0 + }, ); 1; diff --git a/Bugzilla/Constants.pm b/Bugzilla/Constants.pm index 5c9fecc57..62836d8cc 100644 --- a/Bugzilla/Constants.pm +++ b/Bugzilla/Constants.pm @@ -255,7 +255,8 @@ use constant AUTH_NO_SUCH_USER => 5; use constant AUTH_LOCKOUT => 6; # The minimum length a password must have. -use constant USER_PASSWORD_MIN_LENGTH => 6; +# BMO uses 8 characters. +use constant USER_PASSWORD_MIN_LENGTH => 8; use constant LOGIN_OPTIONAL => 0; use constant LOGIN_NORMAL => 1; @@ -424,8 +425,8 @@ use constant MAX_LOGIN_ATTEMPTS => 5; use constant LOGIN_LOCKOUT_INTERVAL => 30; # The maximum number of seconds the Strict-Transport-Security header -# will remain valid. Default is one week. -use constant MAX_STS_AGE => 604800; +# will remain valid. BMO uses one month. +use constant MAX_STS_AGE => 2629744; # Protocols which are considered as safe. use constant SAFE_PROTOCOLS => ('afs', 'cid', 'ftp', 'gopher', 'http', 'https', @@ -438,15 +439,16 @@ use constant LEGAL_CONTENT_TYPES => ('application', 'audio', 'image', 'message', use constant contenttypes => { - "html"=> "text/html" , - "rdf" => "application/rdf+xml" , - "atom"=> "application/atom+xml" , - "xml" => "application/xml" , - "js" => "application/x-javascript" , - "json"=> "application/json" , - "csv" => "text/csv" , - "png" => "image/png" , - "ics" => "text/calendar" , + "html" => "text/html" , + "rdf" => "application/rdf+xml" , + "atom" => "application/atom+xml" , + "xml" => "application/xml" , + "dtd" => "application/xml-dtd" , + "js" => "application/x-javascript" , + "json" => "application/json" , + "csv" => "text/csv" , + "png" => "image/png" , + "ics" => "text/calendar" , }; # Usage modes. Default USAGE_MODE_BROWSER. Use with Bugzilla->usage_mode. diff --git a/Bugzilla/Error.pm b/Bugzilla/Error.pm index 395cc0dc9..17a7a948a 100644 --- a/Bugzilla/Error.pm +++ b/Bugzilla/Error.pm @@ -26,7 +26,7 @@ package Bugzilla::Error; use strict; use base qw(Exporter); -@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError); +@Bugzilla::Error::EXPORT = qw(ThrowCodeError ThrowTemplateError ThrowUserError ThrowErrorPage); use Bugzilla::Constants; use Bugzilla::WebService::Constants; @@ -212,6 +212,44 @@ END exit; } +sub ThrowErrorPage { + # BMO customisation for bug 659231 + my ($template_name, $message) = @_; + + my $dbh = Bugzilla->dbh; + $dbh->bz_rollback_transaction() if $dbh->bz_in_transaction(); + + if (Bugzilla->error_mode == ERROR_MODE_DIE) { + die("error: $message"); + } + + if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT + || Bugzilla->error_mode == ERROR_MODE_JSON_RPC) + { + my $code = ERROR_UNKNOWN_TRANSIENT; + if (Bugzilla->error_mode == ERROR_MODE_DIE_SOAP_FAULT) { + die SOAP::Fault->faultcode($code)->faultstring($message); + } else { + my $server = Bugzilla->_json_server; + $server->raise_error(code => 100000 + $code, + message => $message, + id => $server->{_bz_request_id}, + version => $server->version); + die if _in_eval(); + $server->response($server->error_response_header); + } + } else { + my $cgi = Bugzilla->cgi; + my $template = Bugzilla->template; + my $vars = {}; + $vars->{message} = $message; + print $cgi->header(); + $template->process($template_name, $vars) + || ThrowTemplateError($template->error()); + exit; + } +} + 1; __END__ diff --git a/Bugzilla/Field.pm b/Bugzilla/Field.pm index 219172094..e47e92231 100644 --- a/Bugzilla/Field.pm +++ b/Bugzilla/Field.pm @@ -78,6 +78,8 @@ use Bugzilla::Constants; use Bugzilla::Error; use Bugzilla::Util; use List::MoreUtils qw(any); +use Bugzilla::Config qw(SetParam write_params); +use Bugzilla::Hook; use Scalar::Util qw(blessed); @@ -916,53 +918,64 @@ sub remove_from_db { ThrowUserError('customfield_not_obsolete', {'name' => $self->name }); } - $dbh->bz_start_transaction(); + # BMO: disable bug updates during field creation + # using an eval as try/finally + eval { + SetParam('disable_bug_updates', 1); + write_params(); - # Check to see if bug activity table has records (should be fast with index) - my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity - WHERE fieldid = ?", undef, $self->id); - if ($has_activity) { - ThrowUserError('customfield_has_activity', {'name' => $name }); - } + $dbh->bz_start_transaction(); + + # Check to see if bug activity table has records (should be fast with index) + my $has_activity = $dbh->selectrow_array("SELECT COUNT(*) FROM bugs_activity + WHERE fieldid = ?", undef, $self->id); + if ($has_activity) { + ThrowUserError('customfield_has_activity', {'name' => $name }); + } - # Check to see if bugs table has records (slow) - my $bugs_query = ""; + # Check to see if bugs table has records (slow) + my $bugs_query = ""; - if ($self->type == FIELD_TYPE_MULTI_SELECT) { - $bugs_query = "SELECT COUNT(*) FROM bug_$name"; - } - else { - $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL"; - if ($self->type != FIELD_TYPE_BUG_ID && $self->type != FIELD_TYPE_DATETIME) { - $bugs_query .= " AND $name != ''"; + if ($self->type == FIELD_TYPE_MULTI_SELECT) { + $bugs_query = "SELECT COUNT(*) FROM bug_$name"; } - # Ignore the default single select value - if ($self->type == FIELD_TYPE_SINGLE_SELECT) { - $bugs_query .= " AND $name != '---'"; + else { + $bugs_query = "SELECT COUNT(*) FROM bugs WHERE $name IS NOT NULL"; + if ($self->type != FIELD_TYPE_BUG_ID && $self->type != FIELD_TYPE_DATETIME) { + $bugs_query .= " AND $name != ''"; + } + # Ignore the default single select value + if ($self->type == FIELD_TYPE_SINGLE_SELECT) { + $bugs_query .= " AND $name != '---'"; + } } - } - my $has_bugs = $dbh->selectrow_array($bugs_query); - if ($has_bugs) { - ThrowUserError('customfield_has_contents', {'name' => $name }); - } + my $has_bugs = $dbh->selectrow_array($bugs_query); + if ($has_bugs) { + ThrowUserError('customfield_has_contents', {'name' => $name }); + } - # Once we reach here, we should be OK to delete. - $dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id); + # Once we reach here, we should be OK to delete. + $dbh->do('DELETE FROM fielddefs WHERE id = ?', undef, $self->id); - my $type = $self->type; + my $type = $self->type; - # the values for multi-select are stored in a seperate table - if ($type != FIELD_TYPE_MULTI_SELECT) { - $dbh->bz_drop_column('bugs', $name); - } + # the values for multi-select are stored in a seperate table + if ($type != FIELD_TYPE_MULTI_SELECT) { + $dbh->bz_drop_column('bugs', $name); + } - if ($self->is_select) { - # Delete the table that holds the legal values for this field. - $dbh->bz_drop_field_tables($self); - } + if ($self->is_select) { + # Delete the table that holds the legal values for this field. + $dbh->bz_drop_field_tables($self); + } - $dbh->bz_commit_transaction() + $dbh->bz_commit_transaction(); + }; + my $error = "$@"; + SetParam('disable_bug_updates', 0); + write_params(); + die $error if $error; } =pod @@ -1014,36 +1027,72 @@ sub create { # the parameter isn't sent to create(). $params->{sortkey} = undef if !exists $params->{sortkey}; $params->{type} ||= 0; - - $dbh->bz_start_transaction(); - $class->check_required_create_fields(@_); - my $field_values = $class->run_create_validators($params); - my $visibility_values = delete $field_values->{visibility_values}; - my $field = $class->insert_create_data($field_values); - - $field->set_visibility_values($visibility_values); - $field->_update_visibility_values(); - - $dbh->bz_commit_transaction(); - if ($field->custom) { - my $name = $field->name; - my $type = $field->type; - if (SQL_DEFINITIONS->{$type}) { - # Create the database column that stores the data for this field. - $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type}); + # BMO: disable bug updates during field creation + # using an eval as try/finally + my $field; + eval { + if ($params->{'custom'}) { + SetParam('disable_bug_updates', 1); + write_params(); } - if ($field->is_select) { - # Create the table that holds the legal values for this field. - $dbh->bz_add_field_tables($field); + # Purpose: if the field is active in the fields list before all of the + # data structures are created, anything accessing Bug.pm will crash. So + # stash a copy of the intended obsolete value for later and force it + # to be obsolete on initial creation. + # Upstreaming: https://bugzilla.mozilla.org/show_bug.cgi?id=531243 + my $original_obsolete; + if ($params->{'custom'}) { + $original_obsolete = $params->{'obsolete'}; + $params->{'obsolete'} = 1; } - if ($type == FIELD_TYPE_SINGLE_SELECT) { - # Insert a default value of "---" into the legal values table. - $dbh->do("INSERT INTO $name (value) VALUES ('---')"); + $dbh->bz_start_transaction(); + $class->check_required_create_fields(@_); + my $field_values = $class->run_create_validators($params); + my $visibility_values = delete $field_values->{visibility_values}; + my $field = $class->insert_create_data($field_values); + + $field->set_visibility_values($visibility_values); + $field->_update_visibility_values(); + + $dbh->bz_commit_transaction(); + + if ($field->custom) { + # Restore the obsolete value that got stashed earlier (in memory) + $field->set_obsolete($original_obsolete); + + my $name = $field->name; + my $type = $field->type; + if (SQL_DEFINITIONS->{$type}) { + # Create the database column that stores the data for this field. + $dbh->bz_add_column('bugs', $name, SQL_DEFINITIONS->{$type}); + } + + if ($field->is_select) { + # Create the table that holds the legal values for this field. + $dbh->bz_add_field_tables($field); + } + + if ($type == FIELD_TYPE_SINGLE_SELECT) { + # Insert a default value of "---" into the legal values table. + $dbh->do("INSERT INTO $name (value) VALUES ('---')"); + } + + # Safe to write the original 'obsolete' value to the database now + $field->update; } + }; + + my $error = "$@"; + if ($params->{'custom'}) { + SetParam('disable_bug_updates', 0); + write_params(); } + die $error if $error; + + Bugzilla::Hook::process("field_end_of_create", { field => $field }); return $field; } diff --git a/Bugzilla/Group.pm b/Bugzilla/Group.pm index b7532fe09..3f521d0f2 100644 --- a/Bugzilla/Group.pm +++ b/Bugzilla/Group.pm @@ -119,9 +119,10 @@ sub _get_members { } sub flag_types { - my $self = shift; + my ($self, $params) = @_; + $params ||= {}; require Bugzilla::FlagType; - $self->{flag_types} ||= Bugzilla::FlagType::match({ group => $self->id }); + $self->{flag_types} ||= Bugzilla::FlagType::match({ group => $self->id, %$params }); return $self->{flag_types}; } diff --git a/Bugzilla/Install.pm b/Bugzilla/Install.pm index ce8fe6bad..60e96766b 100644 --- a/Bugzilla/Install.pm +++ b/Bugzilla/Install.pm @@ -93,6 +93,8 @@ sub SETTINGS { # 2011-06-21 glob@mozilla.com -- Bug 589128 email_format => { options => ['html', 'text_only'], default => 'html' }, + # 2011-06-16 glob@mozilla.com -- Bug 663747 + bugmail_new_prefix => { options => ['on', 'off'], default => 'on' }, } }; diff --git a/Bugzilla/Install/DB.pm b/Bugzilla/Install/DB.pm index 84267de41..e8304cfbb 100644 --- a/Bugzilla/Install/DB.pm +++ b/Bugzilla/Install/DB.pm @@ -3210,6 +3210,11 @@ sub _populate_bugs_fulltext { print "Populating bugs_fulltext with $num_bugs entries..."; print " (this can take a long time.)\n"; } + + # As recommended by Monty Widenius for GNOME's upgrade. + # mkanat and justdave concur it'll be helpful for bmo, too. + $dbh->do('SET SESSION myisam_sort_buffer_size = 3221225472'); + my $newline = $dbh->quote("\n"); $dbh->do( qq{$command INTO bugs_fulltext (bug_id, short_desc, comments, diff --git a/Bugzilla/Install/Filesystem.pm b/Bugzilla/Install/Filesystem.pm index 15106dab9..e6783bbee 100644 --- a/Bugzilla/Install/Filesystem.pm +++ b/Bugzilla/Install/Filesystem.pm @@ -169,6 +169,7 @@ sub FILESYSTEM { 'contrib/README' => { perms => OWNER_WRITE }, 'contrib/*/README' => { perms => OWNER_WRITE }, + 'contrib/sendunsentbugmail.pl' => { perms => WS_EXECUTE }, 'docs/bugzilla.ent' => { perms => OWNER_WRITE }, 'docs/makedocs.pl' => { perms => OWNER_EXECUTE }, 'docs/style.css' => { perms => WS_SERVE }, @@ -183,8 +184,10 @@ sub FILESYSTEM { # Directories that we want to set the perms on, but not # recurse through. These are directories we didn't create # in checkesetup.pl. + # + # Purpose of BMO change: unknown. my %non_recurse_dirs = ( - '.' => DIR_WS_SERVE, + '.' => 0755, docs => DIR_WS_SERVE, ); @@ -242,10 +245,13 @@ sub FILESYSTEM { dirs => DIR_WS_SERVE }, "$extensionsdir/*/web" => { files => WS_SERVE, dirs => DIR_WS_SERVE }, - + + # Purpose: allow webserver to read .bzr so we execute bzr commands + # in backticks and look at the result over the web. Used to show + # bzr history. + '.bzr' => { files => WS_SERVE, + dirs => DIR_WS_SERVE }, # Directories only for the owner, not for the webserver. - '.bzr' => { files => OWNER_WRITE, - dirs => DIR_OWNER_WRITE }, t => { files => OWNER_WRITE, dirs => DIR_OWNER_WRITE }, xt => { files => OWNER_WRITE, diff --git a/Bugzilla/Product.pm b/Bugzilla/Product.pm index a0079a033..976804cb0 100644 --- a/Bugzilla/Product.pm +++ b/Bugzilla/Product.pm @@ -779,7 +779,8 @@ sub user_has_access { } sub flag_types { - my $self = shift; + my ($self, $params) = @_; + $params ||= {}; return $self->{'flag_types'} if defined $self->{'flag_types'}; @@ -787,7 +788,7 @@ sub flag_types { my $cache = Bugzilla->request_cache->{flag_types_per_product} ||= {}; $self->{flag_types} = {}; my $prod_id = $self->id; - my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id }); + my $flagtypes = Bugzilla::FlagType::match({ product_id => $prod_id, %$params }); foreach my $type ('bug', 'attachment') { my @flags = grep { $_->target_type eq $type } @$flagtypes; diff --git a/Bugzilla/Search.pm b/Bugzilla/Search.pm index d47e0ae99..a1378e540 100644 --- a/Bugzilla/Search.pm +++ b/Bugzilla/Search.pm @@ -2786,7 +2786,8 @@ sub _changedby { sub IsValidQueryType { my ($queryType) = @_; - if (grep { $_ eq $queryType } qw(specific advanced)) { + # BMO: Added google query type + if (grep { $_ eq $queryType } qw(specific advanced google)) { return 1; } return 0; diff --git a/Bugzilla/Search/Quicksearch.pm b/Bugzilla/Search/Quicksearch.pm index 8425a2be2..1d332e5ae 100644 --- a/Bugzilla/Search/Quicksearch.pm +++ b/Bugzilla/Search/Quicksearch.pm @@ -492,6 +492,9 @@ sub _default_quicksearch_word { addChart('short_desc', 'substring', $word, $negate); addChart('status_whiteboard', 'substring', $word, $negate); addChart('content', 'matches', _matches_phrase($word), $negate) if $fulltext; + + # BMO Bug 664124 - Include the crash signature (sig:) field in default quicksearches + addChart('cf_crash_signature', 'substring', $word, $negate); } sub _handle_urls { diff --git a/Bugzilla/Template.pm b/Bugzilla/Template.pm index 527b704b2..ee8aa4309 100644 --- a/Bugzilla/Template.pm +++ b/Bugzilla/Template.pm @@ -234,7 +234,8 @@ sub quoteUrls { ~<a href=\"mailto:$2\">$1$2</a>~igx; # attachment links - $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[details\])?) + # BMO: Bug 652332 dkl@mozilla.com 2011-07-20 + $text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[diff\])?(?:\s+\[details\])?) ~($things[$count++] = get_attachment_link($2, $1)) && ("\0\0" . ($count-1) . "\0\0") ~egmxi; @@ -293,18 +294,20 @@ sub get_attachment_link { $title = html_quote(clean_text($title)); $link_text =~ s/ \[details\]$//; + $link_text =~ s/ \[diff\]$//; my $linkval = "attachment.cgi?id=$attachid"; - # If the attachment is a patch, try to link to the diff rather - # than the text, by default. + # If the attachment is a patch and patch_viewer feature is + # enabled, add link to the diff. my $patchlink = ""; if ($attachment->ispatch and Bugzilla->feature('patch_viewer')) { - $patchlink = '&action=diff'; + $patchlink = qq| <a href="${linkval}&action=diff" title="$title">[diff]</a>|; } # Whitespace matters here because these links are in <pre> tags. return qq|<span class="$className">| - . qq|<a href="${linkval}${patchlink}" name="attach_${attachid}" title="$title">$link_text</a>| + . qq|<a href="${linkval}" name="attach_${attachid}" title="$title">$link_text</a>| + . qq|${patchlink}| . qq| <a href="${linkval}&action=edit" title="$title">[details]</a>| . qq|</span>|; } @@ -660,6 +663,18 @@ sub create { $var =~ s/>/\\x3e/g; return $var; }, + + # Sadly, different to the above. See http://www.json.org/ + # for details. + json => sub { + my ($var) = @_; + $var =~ s/([\\\"\/])/\\$1/g; + $var =~ s/\n/\\n/g; + $var =~ s/\r/\\r/g; + $var =~ s/\f/\\f/g; + $var =~ s/\t/\\t/g; + return $var; + }, # Converts data to base64 base64 => sub { diff --git a/Bugzilla/Token.pm b/Bugzilla/Token.pm index 86220aa29..4e32436c0 100644 --- a/Bugzilla/Token.pm +++ b/Bugzilla/Token.pm @@ -109,6 +109,8 @@ sub IssueEmailChangeToken { $vars->{'newemailaddress'} = $new_email . $email_suffix; $vars->{'expiration_ts'} = ctime($token_ts + MAX_TOKEN_AGE * 86400); $vars->{'token'} = $token; + # For SecureMail extension + $vars->{'to_user'} = $user; $vars->{'emailaddress'} = $old_email . $email_suffix; my $message; diff --git a/Bugzilla/User.pm b/Bugzilla/User.pm index 849c1d915..5b4f9a814 100644 --- a/Bugzilla/User.pm +++ b/Bugzilla/User.pm @@ -50,6 +50,7 @@ use Bugzilla::Product; use Bugzilla::Classification; use Bugzilla::Field; use Bugzilla::Group; +use Bugzilla::Hook; use DateTime::TimeZone; use List::Util qw(max); @@ -708,8 +709,8 @@ sub bless_groups { return $self->{'bless_groups'} if defined $self->{'bless_groups'}; return [] unless $self->id; - if ($self->in_group('editusers')) { - # Users having editusers permissions may bless all groups. + if ($self->in_group('admin')) { + # Users having admin permissions may bless all groups. $self->{'bless_groups'} = [Bugzilla::Group->get_all]; return $self->{'bless_groups'}; } @@ -2305,7 +2306,7 @@ Determines whether or not a user is in the given group by id. Returns an arrayref of L<Bugzilla::Group> objects. The arrayref consists of the groups the user can bless, taking into account -that having editusers permissions means that you can bless all groups, and +that having admin permissions means that you can bless all groups, and that you need to be able to see a group in order to bless it. =item C<get_products_by_permission($group)> diff --git a/Bugzilla/UserAgent.pm b/Bugzilla/UserAgent.pm new file mode 100644 index 000000000..c0cda2586 --- /dev/null +++ b/Bugzilla/UserAgent.pm @@ -0,0 +1,247 @@ +# -*- Mode: perl; indent-tabs-mode: nil -*- +# +# The contents of this file are subject to the Mozilla Public +# License Version 1.1 (the "License"); you may not use this file +# except in compliance with the License. You may obtain a copy of +# the License at http://www.mozilla.org/MPL/ +# +# Software distributed under the License is distributed on an "AS +# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or +# implied. See the License for the specific language governing +# rights and limitations under the License. +# +# The Original Code is the Bugzilla Bug Tracking System. +# +# The Initial Developer of the Original Code is the Mozilla Foundation +# Portions created by the Initial Developer are Copyright (C) 2011 the +# Initial Developer. All Rights Reserved. +# +# Contributor(s): +# Terry Weissman <terry@mozilla.org> +# Dave Miller <justdave@syndicomm.com> +# Joe Robins <jmrobins@tgix.com> +# Gervase Markham <gerv@gerv.net> +# Shane H. W. Travis <travis@sedsystems.ca> +# Nitish Bezzala <nbezzala@yahoo.com> +# Byron Jones <glob@mozilla.com> + +package Bugzilla::UserAgent; + +use strict; +use base qw(Exporter); +our @EXPORT = qw(detect_platform detect_op_sys); + +use Bugzilla::Field; +use List::MoreUtils qw(natatime); + +use constant DEFAULT_VALUE => 'Other'; + +use constant PLATFORMS_MAP => ( + # PowerPC + qr/\(.*PowerPC.*\)/i => ["PowerPC", "Macintosh"], + # AMD64, Intel x86_64 + qr/\(.*[ix0-9]86 (?:on |\()x86_64.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*amd64.*\)/ => ["AMD64", "x86_64", "PC"], + qr/\(.*x86_64.*\)/ => ["AMD64", "x86_64", "PC"], + # Intel IA64 + qr/\(.*IA64.*\)/ => ["IA64", "PC"], + # Intel x86 + qr/\(.*Intel.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*[ix0-9]86.*\)/ => ["IA32", "x86", "PC"], + # Versions of Windows that only run on Intel x86 + qr/\(.*Win(?:dows |)[39M].*\)/ => ["IA32", "x86", "PC"], + qr/\(.*Win(?:dows |)16.*\)/ => ["IA32", "x86", "PC"], + # Sparc + qr/\(.*sparc.*\)/ => ["Sparc", "Sun"], + qr/\(.*sun4.*\)/ => ["Sparc", "Sun"], + # Alpha + qr/\(.*AXP.*\)/i => ["Alpha", "DEC"], + qr/\(.*[ _]Alpha.\D/i => ["Alpha", "DEC"], + qr/\(.*[ _]Alpha\)/i => ["Alpha", "DEC"], + # MIPS + qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"], + qr/\(.*MIPS.*\)/i => ["MIPS", "SGI"], + # 68k + qr/\(.*68K.*\)/ => ["68k", "Macintosh"], + qr/\(.*680[x0]0.*\)/ => ["68k", "Macintosh"], + # HP + qr/\(.*9000.*\)/ => ["PA-RISC", "HP"], + # ARM + qr/\(.*ARM.*\)/ => ["ARM", "PocketPC"], + # PocketPC intentionally before PowerPC + qr/\(.*Windows CE.*PPC.*\)/ => ["ARM", "PocketPC"], + # PowerPC + qr/\(.*PPC.*\)/ => ["PowerPC", "Macintosh"], + qr/\(.*AIX.*\)/ => ["PowerPC", "Macintosh"], + # Stereotypical and broken + qr/\(.*Windows CE.*\)/ => ["ARM", "PocketPC"], + qr/\(.*Macintosh.*\)/ => ["68k", "Macintosh"], + qr/\(.*Mac OS [89].*\)/ => ["68k", "Macintosh"], + qr/\(.*WOW64.*\)/ => ["x86_64"], + qr/\(.*Win64.*\)/ => ["IA64"], + qr/\(Win.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*Win(?:dows[ -])NT.*\)/ => ["IA32", "x86", "PC"], + qr/\(.*OSF.*\)/ => ["Alpha", "DEC"], + qr/\(.*HP-?UX.*\)/i => ["PA-RISC", "HP"], + qr/\(.*IRIX.*\)/i => ["MIPS", "SGI"], + qr/\(.*(SunOS|Solaris).*\)/ => ["Sparc", "Sun"], + # Braindead old browsers who didn't follow convention: + qr/Amiga/ => ["68k", "Macintosh"], + qr/WinMosaic/ => ["IA32", "x86", "PC"], +); + +use constant OS_MAP => ( + # Sun + qr/\(.*Solaris.*\)/ => ["Solaris"], + qr/\(.*SunOS 5.11.*\)/ => [("OpenSolaris", "Opensolaris", "Solaris 11")], + qr/\(.*SunOS 5.10.*\)/ => ["Solaris 10"], + qr/\(.*SunOS 5.9.*\)/ => ["Solaris 9"], + qr/\(.*SunOS 5.8.*\)/ => ["Solaris 8"], + qr/\(.*SunOS 5.7.*\)/ => ["Solaris 7"], + qr/\(.*SunOS 5.6.*\)/ => ["Solaris 6"], + qr/\(.*SunOS 5.5.*\)/ => ["Solaris 5"], + qr/\(.*SunOS 5.*\)/ => ["Solaris"], + qr/\(.*SunOS.*sun4u.*\)/ => ["Solaris"], + qr/\(.*SunOS.*i86pc.*\)/ => ["Solaris"], + qr/\(.*SunOS.*\)/ => ["SunOS"], + # BSD + qr/\(.*BSD\/(?:OS|386).*\)/ => ["BSDI"], + qr/\(.*FreeBSD.*\)/ => ["FreeBSD"], + qr/\(.*OpenBSD.*\)/ => ["OpenBSD"], + qr/\(.*NetBSD.*\)/ => ["NetBSD"], + # Misc POSIX + qr/\(.*IRIX.*\)/ => ["IRIX"], + qr/\(.*OSF.*\)/ => ["OSF/1"], + qr/\(.*Linux.*\)/ => ["Linux"], + qr/\(.*BeOS.*\)/ => ["BeOS"], + qr/\(.*AIX.*\)/ => ["AIX"], + qr/\(.*OS\/2.*\)/ => ["OS/2"], + qr/\(.*QNX.*\)/ => ["Neutrino"], + qr/\(.*VMS.*\)/ => ["OpenVMS"], + qr/\(.*HP-?UX.*\)/ => ["HP-UX"], + # Windows + qr/\(.*Windows XP.*\)/ => ["Windows XP"], + qr/\(.*Windows NT 6\.1.*\)/ => ["Windows 7"], + qr/\(.*Windows NT 6\.0.*\)/ => ["Windows Vista"], + qr/\(.*Windows NT 5\.2.*\)/ => ["Windows Server 2003"], + qr/\(.*Windows NT 5\.1.*\)/ => ["Windows XP"], + qr/\(.*Windows 2000.*\)/ => ["Windows 2000"], + qr/\(.*Windows NT 5.*\)/ => ["Windows 2000"], + qr/\(.*Win.*9[8x].*4\.9.*\)/ => ["Windows ME"], + qr/\(.*Win(?:dows |)M[Ee].*\)/ => ["Windows ME"], + qr/\(.*Win(?:dows |)98.*\)/ => ["Windows 98"], + qr/\(.*Win(?:dows |)95.*\)/ => ["Windows 95"], + qr/\(.*Win(?:dows |)16.*\)/ => ["Windows 3.1"], + qr/\(.*Win(?:dows[ -]|)NT.*\)/ => ["Windows NT"], + qr/\(.*Windows.*NT.*\)/ => ["Windows NT"], + # OS X + qr/\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ => ["Mac OS X 10.6"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ => ["Mac OS X 10.5"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.4.*\)/ => ["Mac OS X 10.4"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.3.*\)/ => ["Mac OS X 10.3"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.2.*\)/ => ["Mac OS X 10.2"], + qr/\(.*Mac OS X (?:|Mach-O |\()10.1.*\)/ => ["Mac OS X 10.1"], + # Unfortunately, OS X 10.4 was the first to support Intel. This is fallback + # support because some browsers refused to include the OS Version. + qr/\(.*Intel.*Mac OS X.*\)/ => ["Mac OS X 10.4"], + # OS X 10.3 is the most likely default version of PowerPC Macs + # OS X 10.0 is more for configurations which didn't setup 10.x versions + qr/\(.*Mac OS X.*\)/ => [("Mac OS X 10.3", "Mac OS X 10.0", "Mac OS X")], + qr/\(.*Mac OS 9.*\)/ => [("Mac System 9.x", "Mac System 9.0")], + qr/\(.*Mac OS 8\.6.*\)/ => [("Mac System 8.6", "Mac System 8.5")], + qr/\(.*Mac OS 8\.5.*\)/ => ["Mac System 8.5"], + qr/\(.*Mac OS 8\.1.*\)/ => [("Mac System 8.1", "Mac System 8.0")], + qr/\(.*Mac OS 8\.0.*\)/ => ["Mac System 8.0"], + qr/\(.*Mac OS 8[^.].*\)/ => ["Mac System 8.0"], + qr/\(.*Mac OS 8.*\)/ => ["Mac System 8.6"], + qr/\(.*Darwin.*\)/ => [("Mac OS X 10.0", "Mac OS X")], + # Silly + qr/\(.*Mac.*PowerPC.*\)/ => ["Mac System 9.x"], + qr/\(.*Mac.*PPC.*\)/ => ["Mac System 9.x"], + qr/\(.*Mac.*68k.*\)/ => ["Mac System 8.0"], + # Evil + qr/Amiga/i => ["Other"], + qr/WinMosaic/ => ["Windows 95"], + qr/\(.*32bit.*\)/ => ["Windows 95"], + qr/\(.*16bit.*\)/ => ["Windows 3.1"], + qr/\(.*PowerPC.*\)/ => ["Mac System 9.x"], + qr/\(.*PPC.*\)/ => ["Mac System 9.x"], + qr/\(.*68K.*\)/ => ["Mac System 8.0"], +); + +sub detect_platform { + my $userAgent = $ENV{'HTTP_USER_AGENT'}; + my @detected; + my $iterator = natatime(2, PLATFORMS_MAP); + while (my($re, $ra) = $iterator->()) { + if ($userAgent =~ $re) { + push @detected, @$ra; + } + } + return _pick_valid_field_value('rep_platform', @detected); +} + +sub detect_op_sys { + my $userAgent = $ENV{'HTTP_USER_AGENT'}; + my @detected; + my $iterator = natatime(2, OS_MAP); + while (my($re, $ra) = $iterator->()) { + if ($userAgent =~ $re) { + push @detected, @$ra; + } + } + push(@detected, "Windows") if grep(/^Windows /, @detected); + push(@detected, "Mac OS") if grep(/^Mac /, @detected); + return _pick_valid_field_value('op_sys', @detected); +} + +# Takes the name of a field and a list of possible values for that field. +# Returns the first value in the list that is actually a valid value for that +# field. +# Returns 'Other' if none of the values match. +sub _pick_valid_field_value { + my ($field, @values) = @_; + foreach my $value (@values) { + return $value if check_field($field, $value, undef, 1); + } + return DEFAULT_VALUE; +} + +1; + +__END__ + +=head1 NAME + +Bugzilla::UserAgent - UserAgent utilities for Bugzilla + +=head1 SYNOPSIS + + use Bugzilla::UserAgent; + printf "platform: %s op-sys: %s\n", detect_platform(), detect_op_sys(); + +=head1 DESCRIPTION + +The functions exported by this module all return information derived from the +remote client's user agent. + +=head1 FUNCTIONS + +=over 4 + +=item C<detect_platform> + +This function attempts to detect the remote client's platform from the +presented user-agent. If a suitable value on the I<platform> field is found, +that field value will be returned. If no suitable value is detected, +C<detect_platform> returns I<Other>. + +=item C<detect_op_sys> + +This function attempts to detect the remote client's operating system from the +presented user-agent. If a suitable value on the I<op_sys> field is found, that +field value will be returned. If no suitable value is detected, +C<detect_op_sys> returns I<Other>. + +=back + diff --git a/Bugzilla/WebService/Bug.pm b/Bugzilla/WebService/Bug.pm index 723550b09..8506c4b47 100644 --- a/Bugzilla/WebService/Bug.pm +++ b/Bugzilla/WebService/Bug.pm @@ -439,10 +439,17 @@ sub search { delete $match_params{'include_fields'}; delete $match_params{'exclude_fields'}; + my $count_only = delete $match_params{count_only}; + my $bugs = Bugzilla::Bug->match(\%match_params); my $visible = Bugzilla->user->visible_bugs($bugs); - my @hashes = map { $self->_bug_to_hash($_, $params) } @$visible; - return { bugs => \@hashes }; + if ($count_only) { + return { bug_count => scalar @$visible }; + } + else { + my @hashes = map { $self->_bug_to_hash($_, $params) } @$visible; + return { bugs => \@hashes }; + } } sub possible_duplicates { @@ -469,6 +476,12 @@ sub possible_duplicates { sub update { my ($self, $params) = validate(@_, 'ids'); + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + my $user = Bugzilla->login(LOGIN_REQUIRED); my $dbh = Bugzilla->dbh; @@ -563,6 +576,13 @@ sub update { sub create { my ($self, $params) = @_; + + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + Bugzilla->login(LOGIN_REQUIRED); $params = Bugzilla::Bug::map_fields($params); my $bug = Bugzilla::Bug->create($params); @@ -625,6 +645,12 @@ sub add_attachment { my ($self, $params) = validate(@_, 'ids'); my $dbh = Bugzilla->dbh; + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + Bugzilla->login(LOGIN_REQUIRED); defined $params->{ids} || ThrowCodeError('param_required', { param => 'ids' }); @@ -670,6 +696,12 @@ sub add_attachment { sub add_comment { my ($self, $params) = @_; + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + #The user must login in order add a comment Bugzilla->login(LOGIN_REQUIRED); @@ -714,6 +746,12 @@ sub add_comment { sub update_see_also { my ($self, $params) = @_; + # BMO: Don't allow updating of bugs if disabled + if (Bugzilla->params->{disable_bug_updates}) { + ThrowErrorPage('bug/process/updates-disabled.html.tmpl', + 'Bug updates are currently disabled.'); + } + my $user = Bugzilla->login(LOGIN_REQUIRED); # Check parameters @@ -2160,6 +2198,11 @@ C<string> Search the "Status Whiteboard" field on bugs for a substring. Works the same as the C<summary> field described above, but searches the Status Whiteboard field. +=item C<count_only> + +C<boolean> If count_only set to true, only a single hash key called C<bug_count> +will be returned which is the number of bugs that matched the search. + =back =item B<Returns> diff --git a/Bugzilla/WebService/User.pm b/Bugzilla/WebService/User.pm index 2d9276cc3..984f41c8a 100644 --- a/Bugzilla/WebService/User.pm +++ b/Bugzilla/WebService/User.pm @@ -30,6 +30,7 @@ use Bugzilla::User; use Bugzilla::Util qw(trim); use Bugzilla::Token; use Bugzilla::WebService::Util qw(filter validate); +use Bugzilla::Hook; # Don't need auth to login use constant LOGIN_EXEMPT => { @@ -235,6 +236,9 @@ sub get { }} @$in_group; } + Bugzilla::Hook::process('webservice_user_get', + { webservice => $self, params => $params, users => \@users }); + return { users => \@users }; } |