# Don't allow people to retrieve non-cgi executable files or our private data
-<FilesMatch ^(.*\.pm|.*\.pl|.*localconfig.*)$>
+<FilesMatch (\.pm|\.pl|\.tmpl|localconfig.*)$>
deny from all
</FilesMatch>
<FilesMatch ^(localconfig.js|localconfig.rdf)$>
}
}
- return $do_by_name ? \%requested : [values %requested];
+ return $do_by_name ? \%requested
+ : [sort { $a->sortkey <=> $b->sortkey || $a->name cmp $b->name } values %requested];
}
sub active_custom_fields {
=item C<by_name>
If false (or not specified), this method will return an arrayref of
-the requested fields. The order of the returned fields is random.
+the requested fields.
If true, this method will return a hashref of fields, where the keys
are field names and the valules are L<Bugzilla::Field> objects.
$attachment->validate_can_edit($bug->product_id)
|| ThrowUserError('illegal_attachment_edit', { attach_id => $attachment->id });
- $vars->{'description'} = $attachment->description;
-
if ($attachment->bug_id != $bug->bug_id) {
$vars->{'my_bug_id'} = $bug->bug_id;
- $vars->{'attach_bug_id'} = $attachment->bug_id;
ThrowCodeError('mismatched_bug_ids_on_obsolete', $vars);
}
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
- -x_content_type_options => "nosniff",
-expires => '+3M');
disable_utf8();
$reader->iterate_string('Attachment ' . $attachment->id, $attachment->data);
$last_reader->sends_data_to(new PatchReader::DiffPrinter::raw());
# Actually print out the patch.
print $cgi->header(-type => 'text/plain',
- -x_content_type_options => "nosniff",
-expires => '+3M');
disable_utf8();
}
trick_taint($login_cookie);
detaint_natural($user_id);
- my $is_valid =
- $dbh->selectrow_array('SELECT 1
+ my $db_cookie =
+ $dbh->selectrow_array('SELECT cookie
FROM logincookies
WHERE cookie = ?
AND userid = ?
undef, ($login_cookie, $user_id, $ip_addr));
# If the cookie is valid, return a valid username.
- if ($is_valid) {
+ if (defined $db_cookie && $login_cookie eq $db_cookie) {
# If we logged in successfully, then update the lastused
# time on the login cookie
$dbh->do("UPDATE logincookies SET lastused = NOW()
use Bugzilla::Util;
use Net::LDAP;
+use Net::LDAP::Util qw(escape_filter_value);
use constant admin_can_create_account => 0;
use constant user_can_create_account => 0;
sub _bz_search_params {
my ($username) = @_;
+ $username = escape_filter_value($username);
return (base => Bugzilla->params->{"LDAPBaseDN"},
scope => "sub",
filter => '(&(' . Bugzilla->params->{"LDAPuidattribute"}
if ($dbh->FULLTEXT_OR) {
my $joined_terms = join($dbh->FULLTEXT_OR, @words);
($where_sql, $relevance_sql) =
- $dbh->sql_fulltext_search('bugs_fulltext.short_desc',
- $joined_terms, 1);
+ $dbh->sql_fulltext_search('bugs_fulltext.short_desc', $joined_terms);
$relevance_sql ||= $where_sql;
}
else {
my (@where, @relevance);
- my $count = 0;
foreach my $word (@words) {
- $count++;
my ($term, $rel_term) = $dbh->sql_fulltext_search(
- 'bugs_fulltext.short_desc', $word, $count);
+ 'bugs_fulltext.short_desc', $word);
push(@where, $term);
push(@relevance, $rel_term || $term);
}
my $class = shift;
my $params = $class->SUPER::run_create_validators(@_);
+ # Add classification for checking mandatory fields which depend on it
+ $params->{classification} = $params->{product}->classification->name;
+
+ my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1,
+ enter_bug => 1,
+ obsolete => 0 }) };
+ foreach my $field (@mandatory_fields) {
+ $class->_check_field_is_mandatory($params->{$field->name}, $field,
+ $params);
+ }
+
my $product = delete $params->{product};
$params->{product_id} = $product->id;
my $component = delete $params->{component};
delete $params->{resolution};
delete $params->{lastdiffed};
delete $params->{bug_id};
+ delete $params->{classification};
Bugzilla::Hook::process('bug_end_of_create_validators',
{ params => $params });
- my @mandatory_fields = @{ Bugzilla->fields({ is_mandatory => 1,
- enter_bug => 1,
- obsolete => 0 }) };
- foreach my $field (@mandatory_fields) {
- $class->_check_field_is_mandatory($params->{$field->name}, $field,
- $params);
- }
-
return $params;
}
}
# Check if a comment is required for this change.
- if ($new_status->comment_required_on_change_from($old_status) && !$comment)
+ if ($new_status->comment_required_on_change_from($old_status) && !$comment->{'thetext'})
{
ThrowUserError('comment_required', { old => $old_status,
new => $new_status });
$name || ThrowUserError("require_component");
my $product = blessed($invocant) ? $invocant->product_obj
: $params->{product};
- my $obj = Bugzilla::Component->check({ product => $product, name => $name });
- return $obj;
+ my $old_comp = blessed($invocant) ? $invocant->component : '';
+ my $object = Bugzilla::Component->check({ product => $product, name => $name });
+ if ($object->name ne $old_comp && !$object->is_active) {
+ ThrowUserError('value_inactive', { class => ref($object), value => $name });
+ }
+ return $object;
}
sub _check_creation_ts {
my ($invocant, $target, undef, $params) = @_;
my $product = blessed($invocant) ? $invocant->product_obj
: $params->{product};
+ my $old_target = blessed($invocant) ? $invocant->target_milestone : '';
$target = trim($target);
$target = $product->default_milestone if !defined $target;
my $object = Bugzilla::Milestone->check(
{ product => $product, name => $target });
+ if ($old_target && $object->name ne $old_target && !$object->is_active) {
+ ThrowUserError('value_inactive', { class => ref($object), value => $target });
+ }
return $object->name;
}
$version = trim($version);
my $product = blessed($invocant) ? $invocant->product_obj
: $params->{product};
- my $object =
- Bugzilla::Version->check({ product => $product, name => $version });
+ my $old_vers = blessed($invocant) ? $invocant->version : '';
+ my $object = Bugzilla::Version->check({ product => $product, name => $version });
+ if ($object->name ne $old_vers && !$object->is_active) {
+ ThrowUserError('value_inactive', { class => ref($object), value => $version });
+ }
return $object->name;
}
return if !$field->is_visible_on_bug($params || $invocant);
+ return if ($field->type == FIELD_TYPE_SINGLE_SELECT
+ && scalar @{ get_legal_field_values($field->name) } == 1);
+
+ return if ($field->type == FIELD_TYPE_MULTI_SELECT
+ && !scalar @{ get_legal_field_values($field->name) });
+
if (ref($value) eq 'ARRAY') {
$value = join('', @$value);
}
milestone => $milestone_ok ? $self->target_milestone
: $product->default_milestone
};
- $vars{components} = [map { $_->name } @{$product->components}];
- $vars{milestones} = [map { $_->name } @{$product->milestones}];
- $vars{versions} = [map { $_->name } @{$product->versions}];
+ $vars{components} = [map { $_->name } grep($_->is_active, @{$product->components})];
+ $vars{milestones} = [map { $_->name } grep($_->is_active, @{$product->milestones})];
+ $vars{versions} = [map { $_->name } grep($_->is_active, @{$product->versions})];
}
if (!$verified) {
# ref bug id for sending changes email.
my $ref_bug = delete $field_values->{ref_bug};
if ($class->isa('Bugzilla::BugUrl::Bugzilla::Local')
- and !$skip_recursion)
+ and !$skip_recursion
+ and $ref_bug->check_can_change_field('see_also', '', $self->id, \$privs))
{
$ref_bug->add_see_also($self->id, 'skip_recursion');
push @{ $self->{_update_ref_bugs} }, $ref_bug;
# we need to notify changes for that bug too.
$removed_bug_url = $removed_bug_url->[0];
if (!$skip_recursion and $removed_bug_url
- and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local'))
+ and $removed_bug_url->isa('Bugzilla::BugUrl::Bugzilla::Local')
+ and $removed_bug_url->ref_bug_url)
{
my $ref_bug
= Bugzilla::Bug->check($removed_bug_url->ref_bug_url->bug_id);
- if (Bugzilla->user->can_edit_product($ref_bug->product_id)) {
+ if (Bugzilla->user->can_edit_product($ref_bug->product_id)
+ and $ref_bug->check_can_change_field('see_also', $self->id, '', \$privs))
+ {
my $self_url = $removed_bug_url->local_uri($self->id);
$ref_bug->remove_see_also($self_url, 'skip_recursion');
push @{ $self->{_update_ref_bugs} }, $ref_bug;
# Subroutines
#####################################################################
-# Represents which fields from the bugs table are handled by process_bug.cgi.
+# Returns a list of currently active and editable bug fields,
+# including multi-select fields.
sub editable_bug_fields {
my @fields = Bugzilla->dbh->bz_table_columns('bugs');
+ # Add multi-select fields
+ push(@fields, map { $_->name } @{Bugzilla->fields({obsolete => 0,
+ type => FIELD_TYPE_MULTI_SELECT})});
# Obsolete custom fields are not editable.
my @obsolete_fields = @{ Bugzilla->fields({obsolete => 1, custom => 1}) };
@obsolete_fields = map { $_->name } @obsolete_fields;
"lastdiffed", @obsolete_fields)
{
my $location = firstidx { $_ eq $remove } @fields;
- # Custom multi-select fields are not stored in the bugs table.
+ # Ensure field exists before attempting to remove it.
splice(@fields, $location, 1) if ($location > -1);
}
# Sorted because the old @::log_columns variable, which this replaces,
# Delete leftovers from the login form
$self->delete('Bugzilla_remember', 'GoAheadAndLogIn');
+ # Delete the token if we're not performing an action which needs it
+ unless ((defined $self->param('remtype')
+ && ($self->param('remtype') eq 'asdefault'
+ || $self->param('remtype') eq 'asnamed'))
+ || (defined $self->param('remaction')
+ && $self->param('remaction') eq 'forget'))
+ {
+ $self->delete("token");
+ }
+
foreach my $num (1,2,3) {
# If there's no value in the email field, delete the related fields.
if (!$self->param("email$num")) {
unshift(@_, '-x_frame_options' => 'SAMEORIGIN');
}
+ # Add X-XSS-Protection header to prevent simple XSS attacks
+ # and enforce the blocking (rather than the rewriting) mode.
+ unshift(@_, '-x_xss_protection' => '1; mode=block');
+
+ # Add X-Content-Type-Options header to prevent browsers sniffing
+ # the MIME type away from the declared Content-Type.
+ unshift(@_, '-x_content_type_options' => 'nosniff');
+
return $self->SUPER::header(@_) || "";
}
sub _fix_utf8 {
my $input = shift;
# The is_utf8 is here in case CGI gets smart about utf8 someday.
- utf8::decode($input) if defined $input && !utf8::is_utf8($input);
+ utf8::decode($input) if defined $input && !ref $input && !utf8::is_utf8($input);
return $input;
}
use strict;
use Bugzilla::Config::Common;
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# to disable these warnings.
+BEGIN {
+ $Return::Value::NO_CLUCK = 1;
+}
use Email::Send;
our $sortkey = 1200;
# CONSTANTS
#
# Bugzilla version
-use constant BUGZILLA_VERSION => "4.2.1";
+use constant BUGZILLA_VERSION => "4.2.7";
# Location of the remote and local XML files to track new releases.
use constant REMOTE_FILE => 'http://updates.bugzilla.org/bugzilla-update.xml';
}
sub sql_in {
- my ($self, $column_name, $in_list_ref) = @_;
- return " $column_name IN (" . join(',', @$in_list_ref) . ") ";
+ my ($self, $column_name, $in_list_ref, $negate) = @_;
+ return " $column_name "
+ . ($negate ? "NOT " : "")
+ . "IN (" . join(',', @$in_list_ref) . ") ";
}
sub sql_fulltext_search {
# hard to fix later. We do this up here because none of the code below
# works if InnoDB is off. (Particularly if we've already converted the
# tables to InnoDB.)
- my ($innodb_on) = @{$self->selectcol_arrayref(
- q{SHOW VARIABLES LIKE '%have_innodb%'}, {Columns=>[2]})};
- if ($innodb_on ne 'YES') {
+ my %engines = @{$self->selectcol_arrayref('SHOW ENGINES', {Columns => [1,2]})};
+ if (!$engines{InnoDB} || $engines{InnoDB} !~ /^(YES|DEFAULT)$/) {
die install_string('mysql_innodb_disabled');
}
$index = name of an index
Returns: An abstract index definition, always in hashref format.
If the index does not exist, the function returns undef.
+
=cut
+
sub bz_index_info_real {
my ($self, $table, $index) = @_;
use constant MIN_LONG_READ_LEN => 32 * 1024;
use constant FULLTEXT_OR => ' OR ';
+our $fulltext_label = 0;
+
sub new {
my ($class, $params) = @_;
my ($user, $pass, $host, $dbname, $port) =
my $dsn = "dbi:Oracle:host=$host;sid=$dbname";
$dsn .= ";port=$port" if $port;
my $attrs = { FetchHashKeyName => 'NAME_lc',
- LongReadLen => max(Bugzilla->params->{'maxattachmentsize'},
+ LongReadLen => max(Bugzilla->params->{'maxattachmentsize'} || 0,
MIN_LONG_READ_LEN) * 1024,
};
my $self = $class->db_new({ dsn => $dsn, user => $user,
sub sql_group_concat {
my ($self, $text, $separator) = @_;
$separator = $self->quote(', ') if !defined $separator;
- return "group_concat(T_CLOB_DELIM($text, $separator))";
+ my ($distinct, $rest) = $text =~/^(\s*DISTINCT\s|)(.+)$/i;
+ return "group_concat($distinct T_CLOB_DELIM(NVL($rest, ' '), $separator))";
}
sub sql_regexp {
return " TO_DATE($date,'J') ";
}
+
sub sql_fulltext_search {
- my ($self, $column, $text, $label) = @_;
+ my ($self, $column, $text) = @_;
$text = $self->quote($text);
trick_taint($text);
- return "CONTAINS($column,$text,$label) > 0", "SCORE($label)";
+ $fulltext_label++;
+ return "CONTAINS($column,$text,$fulltext_label) > 0", "SCORE($fulltext_label)";
}
sub sql_date_format {
}
sub sql_in {
- my ($self, $column_name, $in_list_ref) = @_;
+ my ($self, $column_name, $in_list_ref, $negate) = @_;
my @in_list = @$in_list_ref;
- return $self->SUPER::sql_in($column_name, $in_list_ref) if $#in_list < 1000;
+ return $self->SUPER::sql_in($column_name, $in_list_ref, $negate) if $#in_list < 1000;
my @in_str;
while (@in_list) {
my $length = $#in_list + 1;
my $splice = $length > 1000 ? 1000 : $length;
my @sub_in_list = splice(@in_list, 0, $splice);
push(@in_str,
- $self->SUPER::sql_in($column_name, \@sub_in_list));
+ $self->SUPER::sql_in($column_name, \@sub_in_list, $negate));
}
return "( " . join(" OR ", @in_str) . " )";
}
my $has_from = ($part =~ m/\bFROM\b/io) if $is_select;
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
- $part =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
-
+ # and its CURRENT_DATE is a date+time, so wrap in TRUNC()
+ $part =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
+
# Oracle use SUBSTR instead of SUBSTRING
$part =~ s/\bSUBSTRING\b/SUBSTR/io;
if ($is_select and !$has_from);
# Oracle recognizes CURRENT_DATE, but not CURRENT_DATE()
- $nonstring =~ s/\bCURRENT_DATE\b\(\)/CURRENT_DATE/io;
+ # and its CURRENT_DATE is a date+time, so wrap in TRUNC()
+ $nonstring =~ s/\bCURRENT_DATE\b(?:\(\))?/TRUNC(CURRENT_DATE)/io;
# Oracle use SUBSTR instead of SUBSTRING
$nonstring =~ s/\bSUBSTRING\b/SUBSTR/io;
. " RETURN NUMBER IS BEGIN RETURN LENGTH(COLUMN_NAME); END;");
# Create types for group_concat
- my $t_clob_delim = $self->selectcol_arrayref("
- SELECT TYPE_NAME FROM USER_TYPES WHERE TYPE_NAME=?",
- undef, 'T_CLOB_DELIM');
-
- if ( !@$t_clob_delim ) {
- $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
- . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256));");
- }
+ my $type_exists = $self->selectrow_array("SELECT 1 FROM user_types
+ WHERE type_name = 'T_GROUP_CONCAT'");
+ $self->do("DROP TYPE T_GROUP_CONCAT") if $type_exists;
+ $self->do("CREATE OR REPLACE TYPE T_CLOB_DELIM AS OBJECT "
+ . "( p_CONTENT CLOB, p_DELIMITER VARCHAR2(256)"
+ . ", MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2"
+ . ");");
+ $self->do("CREATE OR REPLACE TYPE BODY T_CLOB_DELIM IS
+ MAP MEMBER FUNCTION T_CLOB_DELIM_ToVarchar return VARCHAR2 is
+ BEGIN
+ RETURN p_CONTENT;
+ END;
+ END;");
$self->do("CREATE OR REPLACE TYPE T_GROUP_CONCAT AS OBJECT
( CLOB_CONTENT CLOB,
$self->SUPER::bz_setup_database(@_);
+ my $sth = $self->prepare("SELECT OBJECT_NAME FROM USER_OBJECTS WHERE OBJECT_NAME = ?");
my @tables = $self->bz_table_list_real();
+
foreach my $table (@tables) {
my @columns = $self->bz_table_columns_real($table);
foreach my $column (@columns) {
my $def = $self->bz_column_info($table, $column);
+ # bz_add_column() before Bugzilla 4.2.3 didn't handle primary keys
+ # correctly (bug 731156). We have to add missing sequences and
+ # triggers ourselves.
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ my $sequence = "${table}_${column}_SEQ";
+ my $exists = $self->selectrow_array($sth, undef, $sequence);
+ if (!$exists) {
+ my @sql = $self->_get_create_seq_ddl($table, $column);
+ $self->do($_) foreach @sql;
+ }
+ }
+
if ($def->{REFERENCES}) {
my $references = $def->{REFERENCES};
my $update = $references->{UPDATE} || 'CASCADE';
$to_table = 'tag';
}
if ( $update =~ /CASCADE/i ){
- my $trigger_name = uc($fk_name . "_UC");
- my $exist_trigger = $self->selectcol_arrayref(
- "SELECT OBJECT_NAME FROM USER_OBJECTS
- WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ my $trigger_name = uc($fk_name . "_UC");
+ my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
if(@$exist_trigger) {
$self->do("DROP TRIGGER $trigger_name");
}
- my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
+ my $tr_str = "CREATE OR REPLACE TRIGGER $trigger_name"
. " AFTER UPDATE OF $to_column ON $to_table "
. " REFERENCING "
. " NEW AS NEW "
. " SET $column = :NEW.$to_column"
. " WHERE $column = :OLD.$to_column;"
. " END $trigger_name;";
- $self->do($tr_str);
- }
- }
- }
- }
+ $self->do($tr_str);
+ }
+ }
+ }
+ }
# Drop the trigger which causes bug 541553
my $trigger_name = "PRODUCTS_MILESTONEURL";
- my $exist_trigger = $self->selectcol_arrayref(
- "SELECT OBJECT_NAME FROM USER_OBJECTS
- WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ my $exist_trigger = $self->selectcol_arrayref($sth, undef, $trigger_name);
if(@$exist_trigger) {
$self->do("DROP TRIGGER $trigger_name");
}
}
+# These two methods have been copied from Bugzilla::DB::Schema::Oracle.
+sub _get_create_seq_ddl {
+ my ($self, $table, $column) = @_;
+
+ my $seq_name = "${table}_${column}_SEQ";
+ my $seq_sql = "CREATE SEQUENCE $seq_name INCREMENT BY 1 START WITH 1 " .
+ "NOMAXVALUE NOCYCLE NOCACHE";
+ my $trigger_sql = $self->_get_create_trigger_ddl($table, $column, $seq_name);
+ return ($seq_sql, $trigger_sql);
+}
+
+sub _get_create_trigger_ddl {
+ my ($self, $table, $column, $seq_name) = @_;
+
+ my $trigger_sql = "CREATE OR REPLACE TRIGGER ${table}_${column}_TR "
+ . " BEFORE INSERT ON $table "
+ . " FOR EACH ROW "
+ . " BEGIN "
+ . " SELECT ${seq_name}.NEXTVAL "
+ . " INTO :NEW.$column FROM DUAL; "
+ . " END;";
+ return $trigger_sql;
+}
+
+############################################################################
+
package Bugzilla::DB::Oracle::st;
use base qw(DBI::st);
my $self = shift;
my ($db) = @_;
my $server_version = $self->SUPER::bz_check_server_version(@_);
- my ($major_version) = $server_version =~ /^(\d+)/;
- # Pg 9 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+ my ($major_version, $minor_version) = $server_version =~ /^0*(\d+)\.0*(\d+)/;
+ # Pg 9.0 requires DBD::Pg 2.17.2 in order to properly read bytea values.
+ # Pg 9.2 requires DBD::Pg 2.19.3 as spclocation no longer exists.
if ($major_version >= 9) {
- local $db->{dbd}->{version} = '2.17.2';
- local $db->{name} = $db->{name} . ' 9+';
+ local $db->{dbd}->{version} = ($minor_version >= 2) ? '2.19.3' : '2.17.2';
+ local $db->{name} = $db->{name} . " ${major_version}.$minor_version";
Bugzilla::DB::_bz_check_dbd(@_);
}
}
sub get_fk_ddl {
+
=item C<_get_fk_ddl>
=over
=over
=item C<$table> - The name of the table the reference is from.
+
=item C<$column> - The name of the column the reference is from
+
=item C<$references> - The C<REFERENCES> hashref from a column.
=back
}
sub get_column {
+
=item C<get_column($table, $column)>
Description: Public method to get the abstract definition of a column.
in the same fashion as) the current version of Schema.
However, it will represent the serialized data instead of
ABSTRACT_SCHEMA.
+
=cut
sub deserialize_abstract {
return $fk_name;
}
+sub get_add_column_ddl {
+ my $self = shift;
+ my ($table, $column, $definition, $init_value) = @_;
+ my @sql;
+
+ # Create sequences and triggers to emulate SERIAL datatypes.
+ if ($definition->{TYPE} =~ /SERIAL/i) {
+ # Clone the definition to not alter the original one.
+ my %def = %$definition;
+ # Oracle requires to define the column is several steps.
+ my $pk = delete $def{PRIMARYKEY};
+ my $notnull = delete $def{NOTNULL};
+ @sql = $self->SUPER::get_add_column_ddl($table, $column, \%def, $init_value);
+ push(@sql, $self->_get_create_seq_ddl($table, $column));
+ push(@sql, "UPDATE $table SET $column = ${table}_${column}_SEQ.NEXTVAL");
+ push(@sql, "ALTER TABLE $table MODIFY $column NOT NULL") if $notnull;
+ push(@sql, "ALTER TABLE $table ADD PRIMARY KEY ($column)") if $pk;
+ }
+ else {
+ @sql = $self->SUPER::get_add_column_ddl(@_);
+ # Create triggers to deal with empty string.
+ if ($definition->{TYPE} =~ /varchar|TEXT/i && $definition->{NOTNULL}) {
+ push(@sql, _get_notnull_trigger_ddl($table, $column));
+ }
+ }
+
+ return @sql;
+}
+
sub get_alter_column_ddl {
my ($self, $table, $column, $new_def, $set_nulls_to) = @_;
return @sql;
}
+sub get_drop_column_ddl {
+ my $self = shift;
+ my ($table, $column) = @_;
+ my @sql;
+ push(@sql, $self->SUPER::get_drop_column_ddl(@_));
+ my $dbh=Bugzilla->dbh;
+ my $trigger_name = uc($table . "_" . $column);
+ my $exist_trigger = $dbh->selectcol_arrayref(
+ "SELECT OBJECT_NAME FROM USER_OBJECTS
+ WHERE OBJECT_NAME = ?", undef, $trigger_name);
+ if(@$exist_trigger) {
+ push(@sql, "DROP TRIGGER $trigger_name");
+ }
+ # If this column is of type SERIAL, we need to drop the sequence
+ # and trigger that went along with it.
+ my $def = $self->get_column_abstract($table, $column);
+ if ($def->{TYPE} =~ /SERIAL/i) {
+ push(@sql, "DROP SEQUENCE ${table}_${column}_SEQ");
+ push(@sql, "DROP TRIGGER ${table}_${column}_TR");
+ }
+ return @sql;
+}
+
sub get_rename_table_sql {
my ($self, $old_name, $new_name) = @_;
if (lc($old_name) eq lc($new_name)) {
return @sql;
}
-sub get_drop_column_ddl {
- my $self = shift;
- my ($table, $column) = @_;
- my @sql;
- push(@sql, $self->SUPER::get_drop_column_ddl(@_));
- my $dbh=Bugzilla->dbh;
- my $trigger_name = uc($table . "_" . $column);
- my $exist_trigger = $dbh->selectcol_arrayref(
- "SELECT OBJECT_NAME FROM USER_OBJECTS
- WHERE OBJECT_NAME = ?", undef, $trigger_name);
- if(@$exist_trigger) {
- push(@sql, "DROP TRIGGER $trigger_name");
- }
- return @sql;
-}
-
1;
} #eosub--_initialize
#--------------------------------------------------------------------
+sub get_create_database_sql {
+ my ($self, $name) = @_;
+ # We only create as utf8 if we have no params (meaning we're doing
+ # a new installation) or if the utf8 param is on.
+ my $create_utf8 = Bugzilla->params->{'utf8'}
+ || !defined Bugzilla->params->{'utf8'};
+ my $charset = $create_utf8 ? "ENCODING 'UTF8' TEMPLATE template0" : '';
+ return ("CREATE DATABASE $name $charset");
+}
+
sub get_rename_column_ddl {
my ($self, $table, $old_name, $new_name) = @_;
if (lc($old_name) eq lc($new_name)) {
# the parameter isn't sent to create().
$params->{sortkey} = undef if !exists $params->{sortkey};
$params->{type} ||= 0;
-
+ # We mark the custom field as obsolete till it has been fully created,
+ # to avoid race conditions when viewing bugs at the same time.
+ my $is_obsolete = $params->{obsolete};
+ $params->{obsolete} = 1 if $params->{custom};
+
$dbh->bz_start_transaction();
$class->check_required_create_fields(@_);
my $field_values = $class->run_create_validators($params);
# Insert a default value of "---" into the legal values table.
$dbh->do("INSERT INTO $name (value) VALUES ('---')");
}
+
+ # Restore the original obsolete state of the custom field.
+ $dbh->do('UPDATE fielddefs SET obsolete = 0 WHERE id = ?', undef, $field->id)
+ unless $is_obsolete;
}
return $field;
# This allows bug/create/create.html.tmpl to pass in a hashref that
# looks like a bug object.
my $value = blessed($bug) ? $bug->$field_name : $bug->{$field_name};
+ $value = $value->name if blessed($value);
return 0 if !defined $value;
if ($self->field->type == FIELD_TYPE_BUG_URLS
description => \&_check_description,
cc_list => \&_check_cc_list,
target_type => \&_check_target_type,
- sortkey => \&_check_sortey,
+ sortkey => \&_check_sortkey,
is_active => \&Bugzilla::Object::check_boolean,
is_requestable => \&Bugzilla::Object::check_boolean,
is_requesteeble => \&Bugzilla::Object::check_boolean,
return $target_type;
}
-sub _check_sortey {
+sub _check_sortkey {
my ($invocant, $sortkey) = @_;
(detaint_natural($sortkey) && $sortkey <= MAX_SMALLINT)
}
if ($criteria->{product_id}) {
my $product_id = $criteria->{product_id};
-
+ detaint_natural($product_id)
+ || ThrowCodeError('bad_arg', { argument => 'product_id',
+ function => 'Bugzilla::FlagType::sqlify_criteria' });
+
# Add inclusions to the query, which simply involves joining the table
# by flag type ID and target product/component.
push(@$tables, "INNER JOIN flaginclusions AS i ON flagtypes.id = i.type_id");
my $addl_join_clause = "";
if ($criteria->{component_id}) {
my $component_id = $criteria->{component_id};
+ detaint_natural($component_id)
+ || ThrowCodeError('bad_arg', { argument => 'component_id',
+ function => 'Bugzilla::FlagType::sqlify_criteria' });
+
push(@criteria, "(i.component_id = $component_id OR i.component_id IS NULL)");
$join_clause .= "AND (e.component_id = $component_id OR e.component_id IS NULL) ";
}
}
if ($criteria->{group}) {
my $gid = $criteria->{group};
- detaint_natural($gid);
+ detaint_natural($gid)
+ || ThrowCodeError('bad_arg', { argument => 'group',
+ function => 'Bugzilla::FlagType::sqlify_criteria' });
+
push(@criteria, "(flagtypes.grant_group_id = $gid " .
" OR flagtypes.request_group_id = $gid)");
}
my $self = shift;
my $user = Bugzilla->user;
return if !Bugzilla->params->{'usevisibilitygroups'};
- my $is_visible = grep { $_->id == $_ } @{ $user->visible_groups_inherited };
+
+ my $group_id = $self->id;
+ my $is_visible = grep { $_ == $group_id } @{ $user->visible_groups_inherited };
if (!$is_visible) {
ThrowUserError('group_not_visible', { group => $self });
}
not a bug comment (but could still be some other part of a bug, like
the summary line).
+=item C<user>
+
+The L<Bugzilla::User> object representing the user who will see the text.
+This is useful to determine how much confidential information can be displayed
+to the user.
+
=back
=head2 buglist_columns
VALUES (?, ?)');
my $sth_nq = $dbh->prepare('UPDATE namedqueries SET query = ?
WHERE id = ?');
- my $sth_nq_footer = $dbh->prepare(
- 'DELETE FROM namedqueries_link_in_footer
- WHERE user_id = ? AND namedquery_id = ?');
if (scalar @$tags) {
print install_string('update_queries_to_tags'), "\n";
next if !$bug_id;
$sth_bug_tag->execute($bug_id, $tag_id);
}
-
+
# Existing tags may be used in whines, or shared with
# other users. So we convert them rather than delete them.
$uri->query_param('tag', $tag_name);
$sth_nq->execute($uri->query, $query_id);
- # But we don't keep showing them in the footer.
- $sth_nq_footer->execute($user_id, $query_id);
}
$dbh->bz_commit_transaction();
use Safe;
use Term::ANSIColor;
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# in have_vers() to disable these warnings.
+BEGIN {
+ $Return::Value::NO_CLUCK = 1;
+}
+
use base qw(Exporter);
our @EXPORT = qw(
REQUIRED_MODULES
( (!$output and @{$check_results->{missing}})
or ($output and $check_results->{any_missing}) ) ? 1 : 0;
- # We only print the PPM repository note if we have to.
- my $perl_ver = sprintf('%vd', $^V);
- if ($need_module_instructions && ON_ACTIVESTATE && vers_cmp($perl_ver, '5.12') < 0) {
- # URL when running Perl 5.8.x.
- my $url_to_theory58S = 'http://theoryx5.uwinnipeg.ca/ppms';
- # Packages for Perl 5.10 are not compatible with Perl 5.8.
- if (vers_cmp($perl_ver, '5.10') > -1) {
- $url_to_theory58S = 'http://cpan.uwinnipeg.ca/PPMPackages/10xx/';
- }
- print colored(
- install_string('ppm_repo_add',
- { theory_url => $url_to_theory58S }),
- COLOR_ERROR);
-
- # ActivePerls older than revision 819 require an additional command.
- if (ON_ACTIVESTATE < 819) {
- print install_string('ppm_repo_up');
- }
- }
-
if ($need_module_instructions or @{ $check_results->{apache} }) {
# If any output was required, we want to close the "table"
print "*" x TABLE_WIDTH . "\n";
use Encode::MIME::Header;
use Email::Address;
use Email::MIME;
+# Return::Value 1.666002 pollutes the error log with warnings about this
+# deprecated module. We have to set NO_CLUCK = 1 before loading Email::Send
+# to disable these warnings.
+BEGIN {
+ $Return::Value::NO_CLUCK = 1;
+}
use Email::Send;
sub MessageToMTA {
The name of the column that should be considered to be the unique
"name" of this object. The 'name' is a B<string> that uniquely identifies
this Object in the database. Defaults to 'name'. When you specify
-C<{name => $name}> to C<new()>, this is the column that will be
+C<< {name => $name} >> to C<new()>, this is the column that will be
matched against in the DB.
=item C<ID_FIELD>
This is to allow subclasses to have complex parameters, and then to
translate those parameters into C<condition> and C<values> when they
-call C<$self->SUPER::new> (which is this function, usually).
+call C<< $self->SUPER::new >> (which is this function, usually).
If you try to call C<new> outside of a subclass with the C<condition>
and C<values> parameters, Bugzilla will throw an error. These parameters
your subclass's L</ID_FIELD> must be of C<SERIAL>
type in the database.
- Subclass Implementors: This function basically just
- calls L</check_required_create_fields>, then
+Subclass Implementors:
+ This function basically just calls
+ L</check_required_create_fields>, then
L</run_create_validators>, and then finally
L</insert_create_data>. So if you have a complex system that
you need to implement, you can do it by calling these
Returns: A list of objects, or an empty list if there are none.
- Notes: Note that you must call this as C<$class->get_all>. For
- example, C<Bugzilla::Keyword->get_all>.
- C<Bugzilla::Keyword::get_all> will not work.
+ Notes: Note that you must call this as $class->get_all. For
+ example, Bugzilla::Keyword->get_all.
+ Bugzilla::Keyword::get_all will not work.
=back
},
dependson => MULTI_SELECT_OVERRIDE,
keywords => MULTI_SELECT_OVERRIDE,
- 'flagtypes.name' => MULTI_SELECT_OVERRIDE,
+ 'flagtypes.name' => {
+ _non_changed => \&_flagtypes_nonchanged,
+ },
longdesc => {
- %{ MULTI_SELECT_OVERRIDE() },
changedby => \&_long_desc_changedby,
changedbefore => \&_long_desc_changedbefore_after,
changedafter => \&_long_desc_changedbefore_after,
+ _non_changed => \&_long_desc_nonchanged,
},
'longdescs.count' => {
changedby => \&_long_desc_changedby,
my ($self) = @_;
return $self->{sql} if $self->{sql};
my $dbh = Bugzilla->dbh;
-
+
my ($joins, $clause) = $self->_charts_to_conditions();
+
+ if (!$clause->as_string
+ && !Bugzilla->params->{'search_allow_no_criteria'}
+ && !$self->{allow_unlimited})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+
my $select = join(', ', $self->_sql_select);
my $from = $self->_sql_from($joins);
my $where = $self->_sql_where($clause);
}
# These are the columns that we're going to be actually SELECTing.
+sub _display_columns {
+ my ($self) = @_;
+ return @{ $self->{display_columns} } if $self->{display_columns};
+
+ # Do not alter the list from _input_columns at all, even if there are
+ # duplicated columns. Those are passed by the caller, and the caller
+ # expects to get them back in the exact same order.
+ my @columns = $self->_input_columns;
+
+ # Only add columns which are not already listed.
+ my %list = map { $_ => 1 } @columns;
+ foreach my $column ($self->_extra_columns) {
+ push(@columns, $column) unless $list{$column}++;
+ }
+ $self->{display_columns} = \@columns;
+ return @{ $self->{display_columns} };
+}
+
+# These are the columns that are involved in the query.
sub _select_columns {
my ($self) = @_;
return @{ $self->{select_columns} } if $self->{select_columns};
my @select_columns;
- foreach my $column ($self->_input_columns, $self->_extra_columns) {
+ foreach my $column ($self->_display_columns) {
if (my $add_first = COLUMN_DEPENDS->{$column}) {
push(@select_columns, @$add_first);
}
push(@select_columns, $column);
}
-
+ # Remove duplicated columns.
$self->{select_columns} = [uniq @select_columns];
return @{ $self->{select_columns} };
}
-# This takes _select_columns and translates it into the actual SQL that
+# This takes _display_columns and translates it into the actual SQL that
# will go into the SELECT clause.
sub _sql_select {
my ($self) = @_;
my @sql_fields;
- foreach my $column ($self->_select_columns) {
+ foreach my $column ($self->_display_columns) {
my $alias = $column;
# Aliases cannot contain dots in them. We convert them to underscores.
$alias =~ s/\./_/g;
# SQL a bit more readable for debugging.
my $where = join("\n AND ", $self->_standard_where);
my $clause_sql = $main_clause->as_string;
- if ($clause_sql) {
- $where .= "\n AND " . $clause_sql;
- }
- elsif (!Bugzilla->params->{'search_allow_no_criteria'}
- && !$self->{allow_unlimited})
- {
- ThrowUserError('buglist_parameters_required');
- }
+ $where .= "\n AND " . $clause_sql if $clause_sql;
return $where;
}
value => $string_value,
all_values => $value,
joins => [],
+ condition => $condition,
);
$search_args{quoted} = $self->_quote_unless_numeric(\%search_args);
# This should add a "term" selement to %search_args.
sub _do_operator_function {
my ($self, $func_args) = @_;
my $operator = $func_args->{operator};
- my $operator_func = OPERATORS->{$operator};
+ my $operator_func = OPERATORS->{$operator}
+ || ThrowCodeError("search_field_operator_unsupported",
+ { operator => $operator });
$self->$operator_func($func_args);
}
}
sub build_subselect {
- my ($outer, $inner, $table, $cond) = @_;
- return "$outer IN (SELECT $inner FROM $table WHERE $cond)";
+ my ($outer, $inner, $table, $cond, $negate) = @_;
+ # Execute subselects immediately to avoid dependent subqueries, which are
+ # large performance hits on MySql
+ my $q = "SELECT DISTINCT $inner FROM $table WHERE $cond";
+ my $dbh = Bugzilla->dbh;
+ my $list = $dbh->selectcol_arrayref($q);
+ return $negate ? "1=1" : "1=2" unless @$list;
+ return $dbh->sql_in($outer, $list, $negate);
}
# Used by anyexact to get the list of input values. This allows us to
my ($self, $args) = @_;
my $value = $args->{value};
my $user = $self->_user;
-
- if ($value =~ /^\%group/) {
+
+ if ($value =~ /^\%group\.[^%]+%$/) {
$self->_contact_exact_group($args);
}
elsif ($value =~ /^(%\w+%)$/) {
my $dbh = Bugzilla->dbh;
my $user = $self->_user;
+ # We already know $value will match this regexp, else we wouldn't be here.
$value =~ /\%group\.([^%]+)%/;
- my $group = Bugzilla::Group->check({ name => $1, _error => 'invalid_group_name' });
- $group->check_members_are_visible();
+ my $group_name = $1;
+ my $group = Bugzilla::Group->check({ name => $group_name, _error => 'invalid_group_name' });
+ # Pass $group_name instead of $group->name to the error message
+ # to not leak the existence of the group.
$user->in_group($group)
- || ThrowUserError('invalid_group_name', {name => $group->name});
+ || ThrowUserError('invalid_group_name', { name => $group_name });
+ # Now that we know the user belongs to this group, it's safe
+ # to disclose more information.
+ $group->check_members_are_visible();
my $group_ids = Bugzilla::Group->flatten_group_membership($group->id);
my $table = "user_group_map_$chart_id";
my $table = $first_join->{table};
my $columns = "bug_id";
$columns .= ",isprivate" if @{ $first_join->{extra} };
- my $new_table = "SELECT $columns FROM $table AS $as $join_sql";
+ my $new_table = "SELECT DISTINCT $columns FROM $table AS $as $join_sql";
$first_join->{table} = "($new_table)";
# We always want to LEFT JOIN the generated table.
delete $first_join->{join};
};
push(@$joins, $join);
$args->{term} = "$table.bug_when IS NOT NULL";
+
+ # If the user is not part of the insiders group, they cannot see
+ # private comments
+ if (!$self->_user->is_insider) {
+ $args->{term} .= " AND $table.isprivate = 0";
+ }
+}
+
+sub _long_desc_nonchanged {
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins) =
+ @$args{qw(chart_id operator value joins)};
+ my $dbh = Bugzilla->dbh;
+
+ my $table = "longdescs_$chart_id";
+ my $join_args = {
+ chart_id => $chart_id,
+ sequence => $chart_id,
+ field => 'longdesc',
+ full_field => "$table.thetext",
+ operator => $operator,
+ value => $value,
+ all_values => $value,
+ quoted => $dbh->quote($value),
+ joins => [],
+ };
+ $self->_do_operator_function($join_args);
+
+ # If the user is not part of the insiders group, they cannot see
+ # private comments
+ if (!$self->_user->is_insider) {
+ $join_args->{term} .= " AND $table.isprivate = 0";
+ }
+
+ my $join = {
+ table => 'longdescs',
+ as => $table,
+ extra => [ $join_args->{term} ],
+ };
+ push(@$joins, $join);
+
+ $args->{term} = "$table.comment_id IS NOT NULL";
}
sub _content_matches {
my ($chart_id, $joins, $fields, $operator, $value) =
@$args{qw(chart_id joins fields operator value)};
my $dbh = Bugzilla->dbh;
-
+
# "content" is an alias for columns containing text for which we
- # can search a full-text index and retrieve results by relevance,
+ # can search a full-text index and retrieve results by relevance,
# currently just bug comments (and summaries to some degree).
# There's only one way to search a full-text index, so we only
# accept the "matches" operator, which is specific to full-text
# Create search terms to add to the SELECT and WHERE clauses.
my ($term1, $rterm1) =
- $dbh->sql_fulltext_search("$table.$comments_col", $value, 1);
+ $dbh->sql_fulltext_search("$table.$comments_col", $value);
my ($term2, $rterm2) =
- $dbh->sql_fulltext_search("$table.short_desc", $value, 2);
+ $dbh->sql_fulltext_search("$table.short_desc", $value);
$rterm1 = $term1 if !$rterm1;
$rterm2 = $term2 if !$rterm2;
my @terms;
foreach my $word (@words) {
+ next if $word eq '';
$args->{value} = $word;
$args->{quoted} = $dbh->quote($word);
push(@terms, $self->_multiselect_term($args));
}
}
+sub _flagtypes_nonchanged {
+ my ($self, $args) = @_;
+ my ($chart_id, $operator, $value, $joins, $condition) =
+ @$args{qw(chart_id operator value joins condition)};
+ my $dbh = Bugzilla->dbh;
+
+ # For 'not' operators, we need to negate the whole term.
+ # If you search for "Flags" (does not contain) "approval+" we actually want
+ # to return *bugs* that don't contain an approval+ flag. Without rewriting
+ # the negation we'll search for *flags* which don't contain approval+.
+ if ($operator =~ s/^not//) {
+ $args->{operator} = $operator;
+ $condition->operator($operator);
+ $condition->negate(1);
+ }
+
+ my $subselect_args = {
+ chart_id => $chart_id,
+ sequence => $chart_id,
+ field => 'flagtypes.name',
+ full_field => $dbh->sql_string_concat("flagtypes_$chart_id.name", "flags_$chart_id.status"),
+ operator => $operator,
+ value => $value,
+ all_values => $value,
+ quoted => $dbh->quote($value),
+ joins => [],
+ };
+ $self->_do_operator_function($subselect_args);
+ my $subselect_term = $subselect_args->{term};
+
+ # don't call build_subselect as this must run as a true sub-select
+ $args->{term} = "EXISTS (
+ SELECT 1
+ FROM bugs bugs_$chart_id
+ LEFT JOIN attachments AS attachments_$chart_id
+ ON bugs_$chart_id.bug_id = attachments_$chart_id.bug_id
+ LEFT JOIN flags AS flags_$chart_id
+ ON bugs_$chart_id.bug_id = flags_$chart_id.bug_id
+ AND (flags_$chart_id.attach_id = attachments_$chart_id.attach_id
+ OR flags_$chart_id.attach_id IS NULL)
+ LEFT JOIN flagtypes AS flagtypes_$chart_id
+ ON flags_$chart_id.type_id = flagtypes_$chart_id.id
+ WHERE bugs_$chart_id.bug_id = bugs.bug_id
+ AND $subselect_term
+ )";
+}
+
sub _multiselect_nonchanged {
my ($self, $args) = @_;
my ($chart_id, $joins, $field, $operator) =
my $term = $args->{term};
$term .= $args->{_extra_where} || '';
my $select = $args->{_select_field} || 'bug_id';
- my $not_sql = $not ? "NOT " : '';
- return "bugs.bug_id ${not_sql}IN (SELECT $select FROM $table WHERE $term)";
+ return build_subselect("bugs.bug_id", $select, $table, $term, $not);
}
###############################
sub _anywordsubstr {
my ($self, $args) = @_;
- my ($full_field, $value) = @$args{qw(full_field value)};
-
+
my @terms = $self->_substring_terms($args);
$args->{term} = join("\n\tOR ", @terms);
}
sub _allwordssubstr {
my ($self, $args) = @_;
-
+
my @terms = $self->_substring_terms($args);
$args->{term} = join("\n\tAND ", @terms);
}
extra => ["$table.fieldid = $field_id",
"$table.bug_when $sql_operator $sql_date"],
};
- push(@$joins, $join);
+
$args->{term} = "$table.bug_when IS NOT NULL";
+ $self->_changed_security_check($args, $join);
+ push(@$joins, $join);
}
sub _changedfrom_changedto {
extra => ["$table.fieldid = $field_id",
"$table.$column = $quoted"],
};
- push(@$joins, $join);
$args->{term} = "$table.bug_when IS NOT NULL";
+ $self->_changed_security_check($args, $join);
+ push(@$joins, $join);
}
sub _changedby {
extra => ["$table.fieldid = $field_id",
"$table.who = $user_id"],
};
- push(@$joins, $join);
+
$args->{term} = "$table.bug_when IS NOT NULL";
+ $self->_changed_security_check($args, $join);
+ push(@$joins, $join);
+}
+
+sub _changed_security_check {
+ my ($self, $args, $join) = @_;
+ my ($chart_id, $field) = @$args{qw(chart_id field)};
+
+ my $field_object = $self->_chart_fields->{$field}
+ || ThrowCodeError("invalid_field_name", { field => $field });
+ my $field_id = $field_object->id;
+
+ # If the user is not part of the insiders group, they cannot see
+ # changes to attachments (including attachment flags) that are private
+ if ($field =~ /^(?:flagtypes\.name$|attach)/ and !$self->_user->is_insider) {
+ $join->{then_to} = {
+ as => "attach_${field_id}_$chart_id",
+ table => 'attachments',
+ from => "act_${field_id}_$chart_id.attach_id",
+ to => 'attach_id',
+ };
+
+ $args->{term} .= " AND COALESCE(attach_${field_id}_$chart_id.isprivate, 0) = 0";
+ }
}
######################
sub as_string {
my ($self) = @_;
- my @strings;
- foreach my $child (@{ $self->children }) {
- next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
- next if $child->isa('Bugzilla::Search::Condition')
- && !$child->translated;
+ if (!$self->{sql}) {
+ my @strings;
+ foreach my $child (@{ $self->children }) {
+ next if $child->isa(__PACKAGE__) && !$child->has_translated_conditions;
+ next if $child->isa('Bugzilla::Search::Condition')
+ && !$child->translated;
- my $string = $child->as_string;
- if ($self->joiner eq 'AND') {
- $string = "( $string )" if $string =~ /OR/;
- }
- else {
- $string = "( $string )" if $string =~ /AND/;
+ my $string = $child->as_string;
+ next unless $string;
+ if ($self->joiner eq 'AND') {
+ $string = "( $string )" if $string =~ /OR/;
+ }
+ else {
+ $string = "( $string )" if $string =~ /AND/;
+ }
+ push(@strings, $string);
}
- push(@strings, $string);
+
+ my $sql = join(' ' . $self->joiner . ' ', @strings);
+ $sql = "NOT( $sql )" if $sql && $self->negate;
+ $self->{sql} = $sql;
}
-
- my $sql = join(' ' . $self->joiner . ' ', @strings);
- $sql = "NOT( $sql )" if $sql && $self->negate;
- return $sql;
+ return $self->{sql};
}
# Search.pm converts URL parameters to Clause objects. This helps do the
}
sub field { return $_[0]->{field} }
-sub operator { return $_[0]->{operator} }
sub value { return $_[0]->{value} }
+sub operator {
+ my ($self, $value) = @_;
+ if (@_ == 2) {
+ $self->{operator} = $value;
+ }
+ return $self->{operator};
+}
+
sub fov {
my ($self) = @_;
return ($self->field, $self->operator, $self->value);
# Flag and requestee shortcut
if ($or_operand =~ /^(?:flag:)?([^\?]+\?)([^\?]*)$/) {
- addChart('flagtypes.name', 'substring', $1, $negate);
- $chart++; $and = $or = 0; # Next chart for boolean AND
- addChart('requestees.login_name', 'substring', $2, $negate);
+ my ($flagtype, $requestee) = ($1, $2);
+ addChart('flagtypes.name', 'substring', $flagtype, $negate);
+ if ($requestee) {
+ # AND
+ $chart++;
+ $and = $or = 0;
+ addChart('requestees.login_name', 'substring', $requestee, $negate);
+ }
return 1;
}
if (!$search->shared_with_group
or !$user->in_group($search->shared_with_group))
{
- ThrowUserError('missing_query', { queryname => $search->name,
+ ThrowUserError('missing_query', { name => $search->name,
sharer_id => $search->user->id });
}
# Pseudo-constant.
sub SAFE_URL_REGEXP {
my $safe_protocols = join('|', SAFE_PROTOCOLS);
- return qr/($safe_protocols):[^\s<>\"]+[\w\/]/i;
+ return qr/($safe_protocols):[^:\s<>\"][^\s<>\"]+[\w\/]/i;
}
# Convert the constants in the Bugzilla::Constants module into a hash we can
# If you want to modify this routine, read the comments carefully
sub quoteUrls {
- my ($text, $bug, $comment) = (@_);
+ my ($text, $bug, $comment, $user) = @_;
return $text unless $text;
+ $user ||= Bugzilla->user;
# We use /g for speed, but uris can have other things inside them
# (http://foo/bug#3 for example). Filtering that out filters valid
my @hook_regexes;
Bugzilla::Hook::process('bug_format_comment',
{ text => \$text, bug => $bug, regexes => \@hook_regexes,
- comment => $comment });
+ comment => $comment, user => $user });
foreach my $re (@hook_regexes) {
my ($match, $replace) = @$re{qw(match replace)};
map { qr/$_/ } grep($_, Bugzilla->params->{'urlbase'},
Bugzilla->params->{'sslbase'})) . ')';
$text =~ s~\b(${urlbase_re}\Qshow_bug.cgi?id=\E([0-9]+)(\#c([0-9]+))?)\b
- ~($things[$count++] = get_bug_link($3, $1, { comment_num => $5 })) &&
+ ~($things[$count++] = get_bug_link($3, $1, { comment_num => $5, user => $user })) &&
("\0\0" . ($count-1) . "\0\0")
~egox;
# attachment links
$text =~ s~\b(attachment\s*\#?\s*(\d+)(?:\s+\[details\])?)
- ~($things[$count++] = get_attachment_link($2, $1)) &&
+ ~($things[$count++] = get_attachment_link($2, $1, $user)) &&
("\0\0" . ($count-1) . "\0\0")
~egmxi;
$text =~ s~\b($bug_re(?:\s*,?\s*$comment_re)?|$comment_re)
~ # We have several choices. $1 here is the link, and $2-4 are set
# depending on which part matched
- (defined($2) ? get_bug_link($2, $1, { comment_num => $3 }) :
+ (defined($2) ? get_bug_link($2, $1, { comment_num => $3, user => $user }) :
"<a href=\"$current_bugurl#c$4\">$1</a>")
~egox;
$text =~ s~(?<=^\*\*\*\ This\ bug\ has\ been\ marked\ as\ a\ duplicate\ of\ )
(\d+)
(?=\ \*\*\*\Z)
- ~get_bug_link($1, $1)
+ ~get_bug_link($1, $1, { user => $user })
~egmx;
# Now remove the encoding hacks in reverse order
# Creates a link to an attachment, including its title.
sub get_attachment_link {
- my ($attachid, $link_text) = @_;
+ my ($attachid, $link_text, $user) = @_;
my $dbh = Bugzilla->dbh;
+ $user ||= Bugzilla->user;
my $attachment = new Bugzilla::Attachment($attachid);
if ($attachment) {
my $title = "";
my $className = "";
- if (Bugzilla->user->can_see_bug($attachment->bug_id)) {
+ if ($user->can_see_bug($attachment->bug_id)
+ && (!$attachment->isprivate || $user->is_insider))
+ {
$title = $attachment->description;
}
if ($attachment->isobsolete) {
sub get_bug_link {
my ($bug, $link_text, $options) = @_;
$options ||= {};
+ $options->{user} ||= Bugzilla->user;
my $dbh = Bugzilla->dbh;
if (defined $bug) {
clean_text => \&Bugzilla::Util::clean_text ,
quoteUrls => [ sub {
- my ($context, $bug, $comment) = @_;
+ my ($context, $bug, $comment, $user) = @_;
return sub {
my $text = shift;
- return quoteUrls($text, $bug, $comment);
+ return quoteUrls($text, $bug, $comment, $user);
};
},
1
1
],
- bug_list_link => sub
- {
- my $buglist = shift;
- return join(", ", map(get_bug_link($_, $_), split(/ *, */, $buglist)));
+ bug_list_link => sub {
+ my ($buglist, $options) = @_;
+ return join(", ", map(get_bug_link($_, $_, $options), split(/ *, */, $buglist)));
},
# In CSV, quotes are doubled, and any value containing a quote or a
}
return \@optional;
},
- 'default_authorizer' => new Bugzilla::Auth(),
+ 'default_authorizer' => sub { return Bugzilla::Auth->new() },
},
};
# Get information about the token being canceled.
trick_taint($token);
- my ($issuedate, $tokentype, $eventdata, $userid) =
- $dbh->selectrow_array('SELECT ' . $dbh->sql_date_format('issuedate') . ',
+ my ($db_token, $issuedate, $tokentype, $eventdata, $userid) =
+ $dbh->selectrow_array('SELECT token, ' . $dbh->sql_date_format('issuedate') . ',
tokentype, eventdata, userid
FROM tokens
WHERE token = ?',
undef, $token);
+ # Some DBs such as MySQL are case-insensitive by default so we do
+ # a quick comparison to make sure the tokens are indeed the same.
+ (defined $db_token && $db_token eq $token)
+ || ThrowCodeError("cancel_token_does_not_exist");
+
# If we are canceling the creation of a new user account, then there
# is no entry in the 'profiles' table.
my $user = new Bugzilla::User($userid);
$token = clean_text($token);
trick_taint($token);
- return $dbh->selectrow_array(
- "SELECT userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
- FROM tokens
+ my @token_data = $dbh->selectrow_array(
+ "SELECT token, userid, " . $dbh->sql_date_format('issuedate') . ", eventdata
+ FROM tokens
WHERE token = ?", undef, $token);
+
+ # Some DBs such as MySQL are case-insensitive by default so we do
+ # a quick comparison to make sure the tokens are indeed the same.
+ my $db_token = shift @token_data;
+ return undef if (!defined $db_token || $db_token ne $token);
+
+ return @token_data;
}
# Deletes specified token
@{$self->get_selectable_products},
@{$self->get_enterable_products};
- return [ values %products ];
+ return [ sort { $a->name cmp $b->name } values %products ];
}
sub check_can_admin_product {
my @logins;
for my $query (@queries) {
$query = trim($query);
+ next if $query eq '';
+
my $users = match(
$query, # match string
$limit, # match limit
my $complexity_level = Bugzilla->params->{password_complexity};
if ($complexity_level eq 'letters_numbers_specialchars') {
ThrowUserError('password_not_complex')
- if ($password !~ /\w/ || $password !~ /\d/ || $password !~ /[[:punct:]]/);
+ if ($password !~ /[[:alpha:]]/ || $password !~ /\d/ || $password !~ /[[:punct:]]/);
} elsif ($complexity_level eq 'letters_numbers') {
ThrowUserError('password_not_complex')
if ($password !~ /[[:lower:]]/ || $password !~ /[[:upper:]]/ || $password !~ /\d/);
=back
+=head2 Saved Recent Bug Lists
+
+=over
+
+=item C<recent_searches>
+
+Returns an arrayref of L<Bugzilla::Search::Recent> objects
+containing the user's recent searches.
+
+=item C<recent_search_containing(bug_id)>
+
+Returns a L<Bugzilla::Search::Recent> object that contains the most recent
+search by the user for the specified bug id. Retuns undef if no match is found.
+
+=item C<recent_search_for(bug)>
+
+Returns a L<Bugzilla::Search::Recent> object that contains a search by the
+user. Uses the list_id of the current loaded page, or the referrer page, and
+the bug id if that fails. Finally it will check the BUGLIST cookie, and create
+an object based on that, or undef if it does not exist.
+
+=item C<save_last_search>
+
+Saves the users most recent search in the database if logged in, or in the
+BUGLIST cookie if not logged in. Parameters are bug_ids, order, vars and
+list_id.
+
+=back
+
=head2 Account Lockout
=over
=item C<can_enter_product($product_name, $warn)>
- Description: Returns 1 if the user can enter bugs into the specified product.
+ Description: Returns a product object if the user can enter bugs into the
+ specified product.
If the user cannot enter bugs into the product, the behavior of
this method depends on the value of $warn:
- if $warn is false (or not given), a 'false' value is returned;
must be thrown if the user cannot enter bugs
into the specified product.
- Returns: 1 if the user can enter bugs into the product,
+ Returns: A product object if the user can enter bugs into the product,
0 if the user cannot enter bugs into the product and if $warn
is false (an error is thrown if $warn is true).
Params: C<$setting_name> - string - the setting name
Returns: boolean - true if the setting already exists in the DB.
-=back
-
=end private
+=back
+
=head1 METHODS
=over 4
sub search {
my ($self, $params) = @_;
-
+
if ( defined($params->{offset}) and !defined($params->{limit}) ) {
ThrowCodeError('param_required',
{ param => 'limit', function => 'Bug.search()' });
}
-
+
+ my $max_results = Bugzilla->params->{max_search_results};
+ unless (defined $params->{limit} && $params->{limit} == 0) {
+ if (!defined $params->{limit} || $params->{limit} > $max_results) {
+ $params->{limit} = $max_results;
+ }
+ }
+ else {
+ delete $params->{limit};
+ delete $params->{offset};
+ }
+
$params = Bugzilla::Bug::map_fields($params);
delete $params->{WHERE};
my $clause = join(' OR ', @likes);
$params->{WHERE}->{"($clause)"} = [map { "\%$_\%" } @strings];
}
-
+
+ # If no other parameters have been passed other than limit and offset
+ # and a WHERE parameter was not created earlier, then we throw error
+ # if system is configured to do so.
+ if (!$params->{WHERE}
+ && !grep(!/(limit|offset)/i, keys %$params)
+ && !Bugzilla->params->{search_allow_no_criteria})
+ {
+ ThrowUserError('buglist_parameters_required');
+ }
+
# We want include_fields and exclude_fields to be passed to
# _bug_to_hash but not to Bugzilla::Bug->match so we copy the
# params and delete those before passing to Bugzilla::Bug->match.
=back
+=head2 possible_duplicates
+
+B<UNSTABLE>
+
+=over
+
+=item B<Description>
+
+Allows a user to find possible duplicate bugs based on a set of keywords
+such as a user may use as a bug summary. Optionally the search can be
+narrowed down to specific products.
+
+=item B<Params>
+
+=over
+
+=item C<summary> (string) B<Required> - A string of keywords defining
+the type of bug you are trying to report.
+
+=item C<products> (array) - One or more product names to narrow the
+duplicate search to. If omitted, all bugs are searched.
+
+=back
+
+=item B<Returns>
+
+The same as L</get>.
+
+Note that you will only be returned information about bugs that you
+can see. Bugs that you can't see will be entirely excluded from the
+results. So, if you want to see private bugs, you will have to first
+log in and I<then> call this method.
+
+=item B<Errors>
+
+=over
+
+=item 50 (Param Required)
+
+You must specify a value for C<summary> containing a string of keywords to
+search for duplicates.
+
+=back
+
+=item B<History>
+
+=over
+
+=item Added in Bugzilla B<4.0>.
+
+=back
+
+=back
=head2 search
=item C<limit>
-C<int> Limit the number of results returned to C<int> records.
+C<int> Limit the number of results returned to C<int> records. If the limit
+is more than zero and higher than the maximum limit set by the administrator,
+then the maximum limit will be used instead. If you set the limit equal to zero,
+then all matching results will be returned instead.
=item C<offset>
-C<int> Used in conjunction with the C<limit> argument, C<offset> defines
-the starting position for the search. For example, given a search that
-would return 100 bugs, setting C<limit> to 10 and C<offset> to 10 would return
+C<int> Used in conjunction with the C<limit> argument, C<offset> defines
+the starting position for the search. For example, given a search that
+would return 100 bugs, setting C<limit> to 10 and C<offset> to 10 would return
bugs 11 through 20 from the set of 100.
=item C<op_sys>
=item B<Errors>
-Currently, this function doesn't throw any special errors (other than
-the ones that all webservice functions can throw). If you specify
-an invalid value for a particular field, you just won't get any results
-for that value.
+If you specify an invalid value for a particular field, you just won't
+get any results for that value.
+
+=over
+
+=item 1000 (Parameters Required)
+
+You may not search without any search terms.
+
+=back
=item B<History>
=item The C<reporter> input parameter was renamed to C<creator>
in Bugzilla B<4.0>.
+=item In B<4.2.6> and newer, added the ability to return all results if
+C<limit> is set equal to zero. Otherwise maximum results returned are limited
+by system configuration.
+
=back
=back
=item B<Description>
This allows you to create a new bug in Bugzilla. If you specify any
-invalid fields, they will be ignored. If you specify any fields you
-are not allowed to set, they will just be set to their defaults or ignored.
+invalid fields, an error will be thrown stating which field is invalid.
+If you specify any fields you are not allowed to set, they will just be
+set to their defaults or ignored.
You cannot currently set all the items here that you can set on enter_bug.cgi.
=item C<data>
-B<Required> C<base64> The content of the attachment.
+B<Required> C<base64> or C<string> The content of the attachment.
+If the content of the attachment is not ASCII text, you must encode
+it in base64 and declare it as the C<base64> type.
=item C<file_name>
group_exists => 801,
empty_group_description => 802,
invalid_regexp => 803,
+ invalid_group_name => 804,
+
+ # Search errors are 1000-1100
+ buglist_parameters_required => 1000,
# Errors thrown by the WebService itself. The ones that are negative
# conform to http://xmlrpc-epi.sourceforge.net/specs/rfc.fault_codes.php
sub handle_login {
my ($self, $class, $method, $full_method) = @_;
+ # Throw error if the supplied class does not exist or the method is private
+ ThrowCodeError('unknown_method', {method => $full_method}) if (!$class or $method =~ /^_/);
+
eval "require $class";
ThrowCodeError('unknown_method', {method => $full_method}) if $@;
return if ($class->login_exempt($method)
# XMLRPC::Transport::HTTP::CGI doesn't know about Bugzilla carrying around
# its cookies in Bugzilla::CGI, so we need to copy them over.
- foreach (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
- $self->response->headers->push_header('Set-Cookie', $_);
+ foreach my $cookie (@{Bugzilla->cgi->{'Bugzilla_cookie_list'}}) {
+ $self->response->headers->push_header('Set-Cookie', $cookie);
+ }
+
+ # Copy across security related headers from Bugzilla::CGI
+ foreach my $header (split(/[\r\n]+/, Bugzilla->cgi->header)) {
+ my ($name, $value) = $header =~ /^([^:]+): (.*)/;
+ if (!$self->response->headers->header($name)) {
+ $self->response->headers->header($name => $value);
+ }
}
}
# If no groups are specified, we return all users.
return $users if (!$group_ids and !$group_names);
+ my $user = Bugzilla->user;
+
my @groups = map { Bugzilla::Group->check({ id => $_ }) }
@{ $group_ids || [] };
- my @name_groups = map { Bugzilla::Group->check($_) }
- @{ $group_names || [] };
- push(@groups, @name_groups);
-
+
+ if ($group_names) {
+ foreach my $name (@$group_names) {
+ my $group = Bugzilla::Group->check({ name => $name, _error => 'invalid_group_name' });
+ $user->in_group($group) || ThrowUserError('invalid_group_name', { name => $name });
+ push(@groups, $group);
+ }
+ }
my @in_group = grep { $self->_user_in_any_group($_, \@groups) }
@$users;
=over
-=item 51 (Bad Login Name or Group Name)
+=item 51 (Bad Login Name or Group ID)
You passed an invalid login name in the "names" array or a bad
-group name/id in the C<groups>/C<group_ids> arguments.
+group ID in the C<group_ids> argument.
=item 304 (Authorization Required)
Logged-out users cannot use the "ids" or "match" arguments to this
function.
+=item 804 (Invalid Group Name)
+
+You passed a group name in the C<groups> argument which either does not
+exist or you do not belong to it.
+
=back
=item B<History>
=item C<include_disabled> added in Bugzilla B<4.0>. Default behavior
for C<match> has changed to only returning enabled accounts.
+=item Error 804 has been added in Bugzilla 4.0.9 and 4.2.4. It's now
+illegal to pass a group name you don't belong to.
+
=back
=back
=head2 validate
-This helps in the validation of parameters passed into the WebSerice
+This helps in the validation of parameters passed into the WebService
methods. Currently it converts listed parameters into an array reference
if the client only passed a single scalar value. It modifies the parameters
hash in place so other parameters should be unaltered.
}
print $cgi->header(-type=>"$contenttype; name=\"$filename\"",
-content_disposition=> "$disposition; filename=\"$filename\"",
- -content_length => $attachment->datasize,
- -x_content_type_options => "nosniff");
+ -content_length => $attachment->datasize);
disable_utf8();
print $attachment->data;
}
$attachment->set_filename(scalar $cgi->param('filename'));
# Now make sure the attachment has not been edited since we loaded the page.
- if (defined $cgi->param('delta_ts')
- && $cgi->param('delta_ts') ne $attachment->modification_time)
- {
- ($vars->{'operations'}) =
- Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $cgi->param('delta_ts'));
+ my $delta_ts = $cgi->param('delta_ts');
+ my $modification_time = $attachment->modification_time;
- # The token contains the old modification_time. We need a new one.
- $cgi->param('token', issue_hash_token([$attachment->id, $attachment->modification_time]));
+ if ($delta_ts && $delta_ts ne $modification_time) {
+ datetime_from($delta_ts)
+ or ThrowCodeError('invalid_timestamp', { timestamp => $delta_ts });
+ ($vars->{'operations'}) =
+ Bugzilla::Bug::GetBugActivity($bug->id, $attachment->id, $delta_ts);
# If the modification date changed but there is no entry in
# the activity table, this means someone commented only.
# In this case, there is no reason to midair.
if (scalar(@{$vars->{'operations'}})) {
- $cgi->param('delta_ts', $attachment->modification_time);
+ $cgi->param('delta_ts', $modification_time);
+ # The token contains the old modification_time. We need a new one.
+ $cgi->param('token', issue_hash_token([$attachment->id, $modification_time]));
+
$vars->{'attachment'} = $attachment;
print $cgi->header();
my $user = Bugzilla->login();
if (length($buffer) == 0) {
- print $cgi->header(-refresh=> '10; URL=query.cgi');
ThrowUserError("buglist_parameters_required");
}
&& exists $ENV{'HTTP_USER_AGENT'}
&& $ENV{'HTTP_USER_AGENT'} =~ /Mozilla.[3-9]/
&& (($ENV{'HTTP_USER_AGENT'} !~ /[Cc]ompatible/) || ($ENV{'HTTP_USER_AGENT'} =~ /MSIE 5.*Mac_PowerPC/))
- && $ENV{'HTTP_USER_AGENT'} !~ /WebKit/
+ && $ENV{'HTTP_USER_AGENT'} !~ /(?:WebKit|Trident|KHTML)/
&& !$agent
&& !defined($cgi->param('serverpush'))
|| $cgi->param('serverpush');
Bugzilla->login(LOGIN_REQUIRED);
my $query = Bugzilla::Search::Saved->check(
- { user => $sharer_id, name => $name });
+ { user => $sharer_id, name => $name, _error => 'missing_query' });
$query->url
|| ThrowUserError("buglist_parameters_required");
# Generate and return the UI (HTML page) from the appropriate template.
$vars->{'message'} = "buglist_query_gone";
$vars->{'namedcmd'} = $qname;
- $vars->{'url'} = "buglist.cgi?newquery=" . url_quote($buffer) . "&cmdtype=doit&remtype=asnamed&newqueryname=" . url_quote($qname);
+ $vars->{'url'} = "buglist.cgi?newquery=" . url_quote($buffer)
+ . "&cmdtype=doit&remtype=asnamed&newqueryname=" . url_quote($qname)
+ . "&token=" . url_quote(issue_hash_token(['savedsearch']));
$template->process("global/message.html.tmpl", $vars)
|| ThrowTemplateError($template->error());
exit;
elsif (($cmdtype eq "doit") && defined $cgi->param('remtype')) {
if ($cgi->param('remtype') eq "asdefault") {
$user = Bugzilla->login(LOGIN_REQUIRED);
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['searchknob']);
+ $buffer = $params->canonicalise_query('cmdtype', 'remtype',
+ 'query_based_on', 'token');
InsertNamedQuery(DEFAULT_QUERY_NAME, $buffer);
$vars->{'message'} = "buglist_new_default_query";
}
if ($cgi->param('debug')
&& Bugzilla->params->{debug_group}
- && Bugzilla->user->in_group(Bugzilla->params->{debug_group})
+ && $user->in_group(Bugzilla->params->{debug_group})
) {
$vars->{'debug'} = 1;
$vars->{'query'} = $query;
# Set 'urlquerypart' once the buglist ID is known.
$vars->{'urlquerypart'} = $params->canonicalise_query('order', 'cmdtype',
- 'query_based_on');
+ 'query_based_on',
+ 'token');
if ($format->{'extension'} eq "csv") {
# We set CSV files to be downloaded, as they are designed for importing
=head2 Retrieving Product Information
-Call C<Product.get_product> with the name of the product you want to know more
-of.
+Call C<Product.get> with the name of the product you want to know more of.
The call will return a C<Bugzilla::Product> object.
=cut
if ($product_name) {
- $soapresult = $proxy->call('Product.get_product', $product_name);
+ $soapresult = $proxy->call('Product.get', {'names' => [$product_name]});
_die_on_fault($soapresult);
- $result = $soapresult->result;
-
- if (ref($result) eq 'HASH') {
- foreach (keys(%$result)) {
- print "$_: $$result{$_}\n";
+ $result = $soapresult->result()->{'products'}->[0];
+
+ # Iterate all entries, the values may be scalars or array refs with hash refs.
+ foreach my $key (sort(keys %$result)) {
+ my $value = $result->{$key};
+
+ if (ref($value)) {
+ my $counter = 0;
+ foreach my $hash (@$value) {
+ while (my ($innerKey, $innerValue) = each %$hash) {
+ print "$key.$counter.$innerKey: $innerValue\n";
+ }
+ ++$counter;
}
- }
- else {
- print "$result\n";
+ }
+ else {
+ print "$key: $value\n"
+ }
}
}
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
-use warnings;
use lib qw(. lib);
use Bugzilla;
foreach my $pair (@translation) {
my ($from, $to) = @$pair;
print "Converting $from to $to...\n";
+ # There is no FK on bugs.bug_status pointing to bug_status.value,
+ # so it's fine to update the bugs table first.
$dbh->do('UPDATE bugs SET bug_status = ? WHERE bug_status = ?',
undef, $to, $from);
# If the new status already exists, just delete the old one, but retain
# the workflow items from it.
- if (my $existing = new Bugzilla::Status({ name => $to })) {
+ my $new_status = new Bugzilla::Status({ name => $to });
+ my $old_status = new Bugzilla::Status({ name => $from });
+
+ if ($new_status && $old_status) {
+ my $to_id = $new_status->id;
+ my $from_id = $old_status->id;
+ # The subselect collects existing transitions from the target bug status.
+ # The main select collects existing transitions from the renamed bug status.
+ # The diff tells us which transitions are missing from the target bug status.
+ my $missing_transitions =
+ $dbh->selectcol_arrayref('SELECT sw1.new_status
+ FROM status_workflow sw1
+ WHERE sw1.old_status = ?
+ AND sw1.new_status NOT IN (SELECT sw2.new_status
+ FROM status_workflow sw2
+ WHERE sw2.old_status = ?)',
+ undef, ($from_id, $to_id));
+
+ $dbh->do('UPDATE status_workflow SET old_status = ? WHERE old_status = ? AND '
+ . $dbh->sql_in('new_status', $missing_transitions),
+ undef, ($to_id, $from_id)) if @$missing_transitions;
+
+ # The subselect collects existing transitions to the target bug status.
+ # The main select collects existing transitions to the renamed bug status.
+ # The diff tells us which transitions are missing to the target bug status.
+ # We have to explicitly exclude NULL from the subselect, because NOT IN
+ # doesn't know what to do with it (neither true nor false) and no data is returned.
+ $missing_transitions =
+ $dbh->selectcol_arrayref('SELECT sw1.old_status
+ FROM status_workflow sw1
+ WHERE sw1.new_status = ?
+ AND sw1.old_status NOT IN (SELECT sw2.old_status
+ FROM status_workflow sw2
+ WHERE sw2.new_status = ?
+ AND sw2.old_status IS NOT NULL)',
+ undef, ($from_id, $to_id));
+
+ $dbh->do('UPDATE status_workflow SET new_status = ? WHERE new_status = ? AND '
+ . $dbh->sql_in('old_status', $missing_transitions),
+ undef, ($to_id, $from_id)) if @$missing_transitions;
+
+ # Delete rows where old_status = new_status, and then the old status itself.
+ $dbh->do('DELETE FROM status_workflow WHERE old_status = new_status');
$dbh->do('DELETE FROM bug_status WHERE value = ?', undef, $from);
}
# Otherwise, rename the old status to the new one.
- else {
+ elsif ($old_status) {
$dbh->do('UPDATE bug_status SET value = ? WHERE value = ?',
undef, $to, $from);
}
For a devel release, simple bump bz-ver and bz-date
-->
-<!ENTITY bz-ver "4.2.1">
+<!ENTITY bz-ver "4.2.7">
<!ENTITY bz-nextver "4.4">
-<!ENTITY bz-date "2012-04-18">
-<!ENTITY current-year "2012">
+<!ENTITY bz-date "2013-10-16">
+<!ENTITY current-year "2013">
<!ENTITY landfillbase "http://landfill.bugzilla.org/bugzilla-4.2-branch/">
<!ENTITY bz "http://www.bugzilla.org/">
<para>
<emphasis>Login Name</emphasis>:
This is generally the user's full email address. However, if you
- have are using the <quote>emailsuffix</quote> parameter, this may
+ are using the <quote>emailsuffix</quote> parameter, this may
just be the user's login name. Note that users can now change their
login names themselves (to any valid email address).
</para>
<emphasis>Type:</emphasis>
The type of field to create. There are
several types available:
- <simplelist>
- <member>
- Bug ID: A field where you can enter the ID of another bug from
- the same Bugzilla installation. To point to a bug in a remote
- installation, use the See Also field instead.
- </member>
- <member>
- Large Text Box: A multiple line box for entering free text.
- </member>
- <member>
- Free Text: A single line box for entering free text.
- </member>
- <member>
- Multiple-Selection Box: A list box where multiple options
- can be selected. After creating this field, it must be edited
- to add the selection options. See
- <xref linkend="edit-values-list" /> for information about
- editing legal values.
- </member>
- <member>
- Drop Down: A list box where only one option can be selected.
- After creating this field, it must be edited to add the
- selection options. See
- <xref linkend="edit-values-list" /> for information about
- editing legal values.
- </member>
- <member>
- Date/Time: A date field. This field appears with a
- calendar widget for choosing the date.
- </member>
- </simplelist>
+ <variablelist>
+ <varlistentry>
+ <term>Bug ID:</term>
+ <listitem>
+ <para>
+ A field where you can enter the ID of another bug from
+ the same Bugzilla installation. To point to a bug in a remote
+ installation, use the See Also field instead.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Large Text Box:</term>
+ <listitem>
+ <para>
+ A multiple line box for entering free text.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Free Text:</term>
+ <listitem>
+ <para>
+ A single line box for entering free text.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Multiple-Selection Box:</term>
+ <listitem>
+ <para>
+ A list box where multiple options
+ can be selected. After creating this field, it must be edited
+ to add the selection options. See
+ <xref linkend="edit-values-list" /> for information about
+ editing legal values.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Drop Down:</term>
+ <listitem>
+ <para>
+ A list box where only one option can be selected.
+ After creating this field, it must be edited to add the
+ selection options. See
+ <xref linkend="edit-values-list" /> for information about
+ editing legal values.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Date/Time:</term>
+ <listitem>
+ <para>
+ A date field. This field appears with a
+ calendar widget for choosing the date.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
</para>
</listitem>
The first method of making customizations is to directly edit the
templates found in <filename>template/en/default</filename>.
This is probably the best way to go about it if you are going to
- be upgrading Bugzilla through CVS, because if you then execute
- a <command>cvs update</command>, any changes you have made will
+ be upgrading Bugzilla through Bzr, because if you then execute
+ a <command>bzr update</command>, any changes you have made will
be merged automagically with the updated versions.
</para>
<note>
<para>
- If you use this method, and CVS conflicts occur during an
+ If you use this method, and Bzr conflicts occur during an
update, the conflicted templates (and possibly other parts
of your installation) will not work until they are resolved.
</para>
The second method of customization should be used if you
use the overwriting method of upgrade, because otherwise
your changes will be lost. This method may also be better if
- you are using the CVS method of upgrading and are going to make major
+ you are using the Bzr method of upgrading and are going to make major
changes, because it is guaranteed that the contents of this directory
will not be touched during an upgrade, and you can then decide whether
to continue using your own templates, or make the effort to merge your
<para>
<ulink url="http://www.bugzilla.org/download/">Download a Bugzilla tarball</ulink>
- (or check it out from CVS) and place
- it in a suitable directory, accessible by the default web server user
+ (or <ulink url="https://wiki.mozilla.org/Bugzilla:Bzr">check it out from Bzr</ulink>)
+ and place it in a suitable directory, accessible by the default web server user
(probably <quote>apache</quote> or <quote>www</quote>).
Good locations are either directly in the web server's document directories or
in <filename>/usr/local</filename> with a symbolic link to the web server's
C:\perl> <command>ppm install <module name></command>
</programlisting>
- <para>
- The best source for the Windows PPM modules needed for Bugzilla
- is probably the theory58S website, which you can add to your list
- of repositories as follows (for Perl 5.8.x):
- </para>
-
- <programlisting>
-<command>ppm repo add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
- </programlisting>
-
- <para>
- If you are using Perl 5.10.x, you cannot use the same PPM modules as Perl
- 5.8.x as they are incompatible. In this case, you should add the following
- repository:
- </para>
- <programlisting>
-<command>ppm repo add theory58S http://cpan.uwinnipeg.ca/PPMPackages/10xx/</command>
- </programlisting>
-
- <note>
- <para>
- In versions prior to 5.8.8 build 819 of PPM the command is
- <programlisting>
-<command>ppm repository add theory58S http://theoryx5.uwinnipeg.ca/ppms/</command>
- </programlisting>
- </para>
- </note>
<note>
<para>
The PPM repository stores modules in 'packages' that may have
<para>
Running Bugzilla on Windows requires the use of ActiveState
Perl 5.8.1 or higher. Many modules already exist in the core
- distribution of ActiveState Perl. Additional modules can be downloaded
- from <ulink url="http://theoryx5.uwinnipeg.ca/ppms/" /> if you use
- Perl 5.8.x or from <ulink url="http://cpan.uwinnipeg.ca/PPMPackages/10xx/" />
- if you use Perl 5.10.x.
+ distribution of ActiveState Perl. If some modules are missing, upgrade
+ ActiveState Perl to at least 5.12; it has all the required modules.
</para>
</note>
<orderedlist>
<listitem>
<para>
- <emphasis>Product and Component</emphasis>:
- Bugs are divided up by Product and Component, with a Product
- having one or more Components in it. For example,
- bugzilla.mozilla.org's "Bugzilla" Product is composed of several
- Components:
- <simplelist>
- <member>
- <emphasis>Administration:</emphasis>
- Administration of a Bugzilla installation.</member>
-
- <member>
- <emphasis>Bugzilla-General:</emphasis>
- Anything that doesn't fit in the other components, or spans
- multiple components.</member>
-
- <member>
- <emphasis>Creating/Changing Bugs:</emphasis>
- Creating, changing, and viewing bugs.</member>
-
- <member>
- <emphasis>Documentation:</emphasis>
- The Bugzilla documentation, including The Bugzilla Guide.</member>
-
- <member>
- <emphasis>Email:</emphasis>
- Anything to do with email sent by Bugzilla.</member>
-
- <member>
- <emphasis>Installation:</emphasis>
- The installation process of Bugzilla.</member>
-
- <member>
- <emphasis>Query/Buglist:</emphasis>
- Anything to do with searching for bugs and viewing the
- buglists.</member>
-
- <member>
- <emphasis>Reporting/Charting:</emphasis>
- Getting reports from Bugzilla.</member>
-
- <member>
- <emphasis>User Accounts:</emphasis>
- Anything about managing a user account from the user's perspective.
- Saved queries, creating accounts, changing passwords, logging in,
- etc.</member>
-
- <member>
- <emphasis>User Interface:</emphasis>
- General issues having to do with the user interface cosmetics (not
- functionality) including cosmetic issues, HTML templates,
- etc.</member>
- </simplelist>
+ <emphasis>Product and Component</emphasis>:
+ Bugs are divided up by Product and Component, with a Product
+ having one or more Components in it. For example,
+ bugzilla.mozilla.org's "Bugzilla" Product is composed of several
+ Components:
+ <variablelist>
+ <varlistentry>
+ <term>Administration:</term>
+ <listitem>
+ <para>
+ Administration of a Bugzilla installation.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Bugzilla-General:</term>
+ <listitem>
+ <para>
+ Anything that doesn't fit in the other components, or spans
+ multiple components.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Creating/Changing Bugs:</term>
+ <listitem>
+ <para>
+ Creating, changing, and viewing bugs.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Documentation:</term>
+ <listitem>
+ <para>
+ The Bugzilla documentation, including The Bugzilla Guide.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Email:</term>
+ <listitem>
+ <para>
+ Anything to do with email sent by Bugzilla.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Installation:</term>
+ <listitem>
+ <para>
+ The installation process of Bugzilla.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Query/Buglist:</term>
+ <listitem>
+ <para>
+ Anything to do with searching for bugs and viewing the
+ buglists.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Reporting/Charting:</term>
+ <listitem>
+ <para>
+ Getting reports from Bugzilla.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>User Accounts:</term>
+ <listitem>
+ <para>
+ Anything about managing a user account from the user's perspective.
+ Saved queries, creating accounts, changing passwords, logging in,
+ etc.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>User Interface:</term>
+ <listitem>
+ <para>
+ General issues having to do with the user interface cosmetics (not
+ functionality) including cosmetic issues, HTML templates,
+ etc.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
</para>
</listitem>
This form can be used for time tracking.
To use this feature, you have to be blessed group membership
specified by the <quote>timetrackinggroup</quote> parameter.
- <simplelist>
- <member>
- <emphasis>Orig. Est.:</emphasis>
- This field shows the original estimated time.</member>
-
- <member>
- <emphasis>Current Est.:</emphasis>
- This field shows the current estimated time.
- This number is calculated from <quote>Hours Worked</quote>
- and <quote>Hours Left</quote>.</member>
-
- <member>
- <emphasis>Hours Worked:</emphasis>
- This field shows the number of hours worked.</member>
-
- <member>
- <emphasis>Hours Left:</emphasis>
- This field shows the <quote>Current Est.</quote> -
- <quote>Hours Worked</quote>.
- This value + <quote>Hours Worked</quote> will become the
- new Current Est.</member>
-
- <member>
- <emphasis>%Complete:</emphasis>
- This field shows what percentage of the task is complete.</member>
-
- <member>
- <emphasis>Gain:</emphasis>
- This field shows the number of hours that the bug is ahead of the
- <quote>Orig. Est.</quote>.</member>
-
- <member>
- <emphasis>Deadline:</emphasis>
- This field shows the deadline for this bug.</member>
- </simplelist>
+ <variablelist>
+ <varlistentry>
+ <term>Orig. Est.:</term>
+ <listitem>
+ <para>
+ This field shows the original estimated time.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Current Est.:</term>
+ <listitem>
+ <para>
+ This field shows the current estimated time.
+ This number is calculated from <quote>Hours Worked</quote>
+ and <quote>Hours Left</quote>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Hours Worked:</term>
+ <listitem>
+ <para>
+ This field shows the number of hours worked.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Hours Left:</term>
+ <listitem>
+ <para>
+ This field shows the <quote>Current Est.</quote> -
+ <quote>Hours Worked</quote>.
+ This value + <quote>Hours Worked</quote> will become the
+ new Current Est.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>%Complete:</term>
+ <listitem>
+ <para>
+ This field shows what percentage of the task is complete.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Gain:</term>
+ <listitem>
+ <para>
+ This field shows the number of hours that the bug is ahead of the
+ <quote>Orig. Est.</quote>.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Deadline:</term>
+ <listitem>
+ <para>
+ This field shows the deadline for this bug.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
</para>
</listitem>
<para>The format of the list is configurable. For example, it can be
sorted by clicking the column headings. Other useful features can be
accessed using the links at the bottom of the list:
- <simplelist>
- <member>
- <emphasis>Long Format:</emphasis>
-
- this gives you a large page with a non-editable summary of the fields
- of each bug.</member>
-
- <member>
- <emphasis>XML:</emphasis>
-
- get the buglist in the XML format.</member>
-
- <member>
- <emphasis>CSV:</emphasis>
-
- get the buglist as comma-separated values, for import into e.g.
- a spreadsheet.</member>
-
- <member>
- <emphasis>Feed:</emphasis>
-
- get the buglist as an Atom feed. Copy this link into your
- favorite feed reader. If you are using Firefox, you can also
- save the list as a live bookmark by clicking the live bookmark
- icon in the status bar. To limit the number of bugs in the feed,
- add a limit=n parameter to the URL.</member>
-
- <member>
- <emphasis>iCalendar:</emphasis>
-
- Get the buglist as an iCalendar file. Each bug is represented as a
- to-do item in the imported calendar.</member>
-
- <member>
- <emphasis>Change Columns:</emphasis>
-
- change the bug attributes which appear in the list.</member>
-
- <member>
- <emphasis>Change several bugs at once:</emphasis>
-
- If your account is sufficiently empowered, and more than one bug
- appear in the bug list, this link is displayed which lets you make
- the same change to all the bugs in the list - for example, changing
- their assignee.</member>
-
- <member>
- <emphasis>Send mail to bug assignees:</emphasis>
-
- If more than one bug appear in the bug list and there are at least
- two distinct bug assignees, this links is displayed which lets you
- easily send a mail to the assignees of all bugs on the list.</member>
-
- <member>
- <emphasis>Edit Search:</emphasis>
-
- If you didn't get exactly the results you were looking for, you can
- return to the Query page through this link and make small revisions
- to the query you just made so you get more accurate results.</member>
-
- <member>
- <emphasis>Remember Search As:</emphasis>
-
- You can give a search a name and remember it; a link will appear
- in your page footer giving you quick access to run it again later.
- </member>
- </simplelist>
+ <variablelist>
+ <varlistentry>
+ <term>Long Format:</term>
+ <listitem>
+ <para>
+ this gives you a large page with a non-editable summary of the fields
+ of each bug.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>XML:</term>
+ <listitem>
+ <para>
+ get the buglist in the XML format.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>CSV:</term>
+ <listitem>
+ <para>
+ get the buglist as comma-separated values, for import into e.g.
+ a spreadsheet.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Feed:</term>
+ <listitem>
+ <para>
+ get the buglist as an Atom feed. Copy this link into your
+ favorite feed reader. If you are using Firefox, you can also
+ save the list as a live bookmark by clicking the live bookmark
+ icon in the status bar. To limit the number of bugs in the feed,
+ add a limit=n parameter to the URL.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>iCalendar:</term>
+ <listitem>
+ <para>
+ Get the buglist as an iCalendar file. Each bug is represented as a
+ to-do item in the imported calendar.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Change Columns:</term>
+ <listitem>
+ <para>
+ change the bug attributes which appear in the list.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Change several bugs at once:</term>
+ <listitem>
+ <para>
+ If your account is sufficiently empowered, and more than one bug
+ appear in the bug list, this link is displayed which lets you make
+ the same change to all the bugs in the list - for example, changing
+ their assignee.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Send mail to bug assignees:</term>
+ <listitem>
+ <para>
+ If more than one bug appear in the bug list and there are at least
+ two distinct bug assignees, this links is displayed which lets you
+ easily send a mail to the assignees of all bugs on the list.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Edit Search:</term>
+ <listitem>
+ <para>
+ If you didn't get exactly the results you were looking for, you can
+ return to the Query page through this link and make small revisions
+ to the query you just made so you get more accurate results.
+ </para>
+ </listitem>
+ </varlistentry>
+
+ <varlistentry>
+ <term>Remember Search As:</term>
+ <listitem>
+ <para>
+ You can give a search a name and remember it; a link will appear
+ in your page footer giving you quick access to run it again later.
+ </para>
+ </listitem>
+ </varlistentry>
+ </variablelist>
</para>
</section>
<title>Adding/removing tags to/from bugs</title>
<para>
You can add and remove tags from individual bugs, which let you find and
- manage them more easily. Creating a new tag automatically generates a saved
- search - whose name is the name of the tag - which lists bugs with this tag.
- This saved search will be displayed in the footer of pages by default, as
- all other saved searches. The main difference between tags and normal saved
- searches is that saved searches, as described in the previous section, are
- stored in the form of a list of matching criteria, while the saved search
- generated by tags is a list of bug numbers. Consequently, you can easily
- edit this list by either adding or removing tags from bugs. To enable this
+ manage bugs more easily. Tags are per-user and so are only visible and editable
+ by the user who created them. You can then run queries using tags as a criteria,
+ either by using the Advanced Search form, or simply by typing "tag:my_tag_name"
+ in the QuickSearch box at the top (or bottom) of the page. To enable this
feature, you have to turn on the <quote>Enable tags for bugs</quote> user
preference, see <xref linkend="userpreferences" />. This feature is disabled
by default.
these bugs and mixing all these reasons, you can now store these bugs in
separate lists, e.g. <quote>Keep in mind</quote>, <quote>Interesting bugs</quote>,
or <quote>Triage</quote>. One big advantage of this way to manage bugs
- is that you can easily add or remove bugs one by one, which is not
- possible to do with saved searches without having to edit the search
- criteria again.
+ is that you can easily add or remove tags from bugs one by one.
</para>
</section>
</section>
my $component_id = $component ? $component->id : 0;
my $show_flag_counts = $cgi->param('show_flag_counts') ? 1 : 0;
my $group_id = $cgi->param('group');
+ if ($group_id) {
+ detaint_natural($group_id) || ThrowUserError('invalid_group_ID');
+ }
my $bug_flagtypes;
my $attach_flagtypes;
my $added_comment;
if (trim($fields{'comment'})) {
- $added_comment = $bug->comments->[-1];
+ # The "old" bug object doesn't contain the comment we just added.
+ $added_comment = Bugzilla::Bug->check($bug_id)->comments->[-1];
}
return ($bug, $added_comment);
}
/\(.*Windows.*NT.*\)/ && do {push @os, "Windows NT";};
};
/\(.*Mac OS X.*\)/ && do {
+ /\(.*Mac OS X (?:|Mach-O |\()10.8.*\)/ && do {push @os, "Mac OS X 10.8";};
/\(.*Mac OS X (?:|Mach-O |\()10.7.*\)/ && do {push @os, "Mac OS X 10.7";};
/\(.*Mac OS X (?:|Mach-O |\()10.6.*\)/ && do {push @os, "Mac OS X 10.6";};
/\(.*Mac OS X (?:|Mach-O |\()10.5.*\)/ && do {push @os, "Mac OS X 10.5";};
my $new_error_msg = "Ah ah, you tried to access $page_id? Good try!";
$new_error_msg = html_quote($new_error_msg);
# There are better tools to parse an HTML page, but it's just an example.
- $$page =~ s/(?<=<td id="error_msg" class="throw_error">).*(?=<\/td>)/$new_error_msg/si;
+ # Since Perl 5.16, we can no longer write "class" inside look-behind
+ # assertions, because "ss" is also seen as the german ß character, which
+ # makes Perl 5.16 complain. The right fix is to use the /aa modifier,
+ # but it's only understood since Perl 5.14. So the workaround is to write
+ # "clas[s]" instead of "class". Stupid and ugly hack, but it works with
+ # all Perl versions.
+ $$page =~ s/(?<=<td id="error_msg" clas[s]="throw_error">).*(?=<\/td>)/$new_error_msg/si;
}
sub flag_end_of_update {
my $original_validator = shift;
my ($invocant, $resolution) = @_;
- if ($resolution eq 'MOVED' and !Bugzilla->input_params->{'oldbugmove'}) {
+ if ($resolution eq 'MOVED' && $invocant->resolution ne 'MOVED'
+ && !Bugzilla->input_params->{'oldbugmove'})
+ {
# MOVED has a special meaning and can only be used when
# really moving bugs to another installation.
ThrowUserError('oldbugmove_no_manual_move');
--- /dev/null
+# -*- 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 Frédéric Buclin.
+# Portions created by the Initial Developer is Copyright (C) 2012 the
+# Initial Developer. All Rights Reserved.
+#
+# Contributor(s):
+# Frédéric Buclin <LpSolit@gmail.com>
+
+package Bugzilla::Extension::Voting;
+use strict;
+
+use constant NAME => 'Voting';
+
+use constant REQUIRED_MODULES => [
+];
+
+use constant OPTIONAL_MODULES => [
+];
+
+__PACKAGE__->NAME;
use List::Util qw(min);
-use constant NAME => 'Voting';
use constant VERSION => BUGZILLA_VERSION;
use constant DEFAULT_VOTES_PER_BUG => 1;
# These came from Bugzilla itself, so they maintain the old numbers
foreach my $comment ( $bug->children('long_desc') ) {
Debug( "Parsing Long Description", DEBUG_LEVEL );
my %long_desc = ( who => $comment->field('who'),
- bug_when => $comment->field('bug_when'),
+ bug_when => format_time($comment->field('bug_when'), '%Y-%m-%d %T'),
isprivate => $comment->{'att'}->{'isprivate'} || 0 );
# If the exporter is not in the insidergroup, keep the comment public.
# Max Kanat-Alexander <mkanat@bugzilla.org>
use strict;
+
+use Cwd qw(abs_path);
use File::Basename;
-BEGIN { chdir dirname($0); }
+BEGIN {
+ # Untaint the abs_path.
+ my ($a) = abs_path($0) =~ /^(.*)$/;
+ chdir dirname($a);
+}
use lib qw(. lib);
use Bugzilla;
return;
var form = YAHOO.util.Dom.getAncestorByTagName(form_member, 'form');
+ // Disable the token field so setForm doesn't include it
+ var reenable_token = false;
+ if (form['token'] && !form['token'].disabled) {
+ form['token'].disabled = true;
+ reenable_token = true;
+ }
var query = YAHOO.util.Connect.setForm(form);
+ if (reenable_token)
+ form['token'].disabled = false;
window.History.replaceState(null, document.title, '?' + query);
}
}
var keywordAutoComp = new YAHOO.widget.AutoComplete(field, container, this.dataSource);
keywordAutoComp.maxResultsDisplayed = YAHOO.bugzilla.keyword_array.length;
+ keywordAutoComp.formatResult = keywordAutoComp.formatEscapedResult;
keywordAutoComp.minQueryLength = 0;
keywordAutoComp.useIFrame = true;
keywordAutoComp.delimChar = [","," "];
this.expandContainer();
}
});
+ keywordAutoComp.dataRequestEvent.subscribe( function(type, args) {
+ args[0].autoHighlight = args[1] != '';
+ });
}
};
if (should_set($dep_field)) {
if (my $dep_action = $cgi->param("${dep_field}_action")) {
$set_all_fields{$dep_field}->{$dep_action} =
- [split(/\s,/, $cgi->param($dep_field))];
+ [split(/[\s,]+/, $cgi->param($dep_field))];
}
else {
$set_all_fields{$dep_field}->{set} = $cgi->param($dep_field);
use Bugzilla::Keyword;
use Bugzilla::Field;
use Bugzilla::Install::Util qw(vers_cmp);
+use Bugzilla::Token;
my $cgi = Bugzilla->cgi;
my $dbh = Bugzilla->dbh;
if ($cgi->param('nukedefaultquery')) {
if ($userid) {
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['nukedefaultquery']);
$dbh->do("DELETE FROM namedqueries" .
" WHERE userid = ? AND name = ?",
undef, ($userid, DEFAULT_QUERY_NAME));
$vars->{'category'} = Bugzilla::Chart::getVisibleSeries();
}
-if ($cgi->param('format') && $cgi->param('format') =~ /^report-(table|graph)$/) {
- # Get legal custom fields for tabular and graphical reports.
- my @custom_fields_for_reports =
- grep { $_->type == FIELD_TYPE_SINGLE_SELECT } Bugzilla->active_custom_fields;
- $vars->{'custom_fields'} = \@custom_fields_for_reports;
-}
-
$vars->{'known_name'} = $cgi->param('known_name');
$vars->{'columnlist'} = $cgi->param('columnlist');
$height <= 2000 || ThrowUserError("chart_too_large");
}
+my $formatparam = $cgi->param('format') || '';
+
# These shenanigans are necessary to make sure that both vertical and
# horizontal 1D tables convert to the correct dimension when you ask to
# display them as some sort of chart.
-if (defined $cgi->param('format') && $cgi->param('format') eq "table") {
+if ($formatparam eq "table") {
if ($col_field && !$row_field) {
# 1D *tables* should be displayed vertically (with a row_field only)
$row_field = $col_field;
$vars->{'tbl_names'} = \@tbl_names;
# Below a certain width, we don't see any bars, so there needs to be a minimum.
-if ($width && $cgi->param('format') eq "bar") {
+if ($width && $formatparam eq "bar") {
my $min_width = (scalar(@col_names) || 1) * 20;
if (!$cgi->param('cumulate')) {
$vars->{'debug'} = 1;
}
-my $formatparam = $cgi->param('format');
-
if ($action eq "wrap") {
# So which template are we using? If action is "wrap", we will be using
# no format (it gets passed through to be the format of the actual data),
# data, or images generated by calling report.cgi again with action as
# "plot".
$formatparam =~ s/[^a-zA-Z\-]//g;
- trick_taint($formatparam);
$vars->{'format'} = $formatparam;
$formatparam = '';
sub get_names {
my ($names, $isnumeric, $field_name) = @_;
my ($field, @sorted);
+ # XXX - This is a hack to handle the actual_time/work_time field,
+ # because it's named 'actual_time' in Search.pm but 'work_time' in Field.pm.
+ $_[2] = $field_name = 'work_time' if $field_name eq 'actual_time';
+
# _realname fields aren't real Bugzilla::Field objects, but they are a
# valid axis, so we don't vailidate them as Bugzilla::Field objects.
$field = Bugzilla::Field->check($field_name)
foreach my $value (@{$field->legal_values}) {
push(@sorted, $value->name) if $names->{$value->name};
}
- unshift(@sorted, ' ') if $field_name eq 'resolution';
+ unshift(@sorted, '---') if $field_name eq 'resolution';
@sorted = uniq @sorted;
}
elsif ($isnumeric) {
else {
$value = shift @$result;
$value = ' ' if (!defined $value || $value eq '');
+ $value = '---' if ($field eq 'resolution' && $value eq ' ');
}
return $value;
}
my $field = shift;
my $cgi = Bugzilla->cgi;
- return join('&', map {"$field=$_"} $cgi->param($field));
+ return join('&', map {url_quote($field) . '=' . url_quote($_)} $cgi->param($field));
}
exit;
}
-my $format = $template->get_format("bug/show", scalar $cgi->param('format'),
- scalar $cgi->param('ctype'));
-
my @bugs;
my %marks;
or warn install_string('chmod_failed', { path => $filename,
error => $! });
-my $urlbase = Bugzilla->params->{'urlbase'};
+my $urlbase = correct_urlbase();
print $fh "digraph G {";
print $fh qq{
/* page title */
#titles {
- -moz-border-radius-topleft: 5px;
- -moz-border-radius-topright: 5px;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
}
#header .links, #footer {
}
#header {
- -moz-border-radius-bottomleft: 5px;
- -moz-border-radius-bottomright: 5px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
border: none;
}
border: 1px solid #747e93;
padding: 10px;
font-size: 10pt;
- -moz-border-radius: 5px;
+ border-radius: 5px;
}
a {
#footer {
border: 1px solid #747e93;
width: 100%;
- -moz-border-radius: 5px;
+ border-radius: 5px;
}
#footer #links-actions,
border-left: 1px solid #747E93;
border-right: 1px solid #747E93;
border-bottom: 1px solid #747E93;
- -moz-border-radius-bottomleft: 5px;
- -moz-border-radius-bottomright: 5px;
+ border-bottom-left-radius: 5px;
+ border-bottom-right-radius: 5px;
padding: 0.5em;
}
width: 100%;
background-color: #404D6C;
color: #fff;
- -moz-border-radius-topleft: 5px;
- -moz-border-radius-topright: 5px;
+ border-top-left-radius: 5px;
+ border-top-right-radius: 5px;
font-size: 110%;
margin: 0;
padding: 0.5em;
margin: 8px 0;
padding: 0.3em;
background-color: rgb(208, 208, 208);
- -moz-border-radius: 0.5em;
+ border-radius: 0.5em;
font-size: 125%;
font-weight: bold;
}
[% IF cgi.request_method == "GET" AND cgi.query_string %]
[% connector = "&" %]
[% END %]
- [% script_name = login_target _ connector _ "GoAheadAndLogIn=1" %]
- <a id="login_link[% qs_suffix %]" href="[% script_name FILTER html %]"
+ [% script_url = login_target _ connector _ "GoAheadAndLogIn=1" %]
+ <a id="login_link[% qs_suffix %]" href="[% script_url FILTER html %]"
onclick="return show_mini_login_form('[% qs_suffix %]')">Log In</a>
[% Hook.process('additional_methods') %]
</li>
<li id="forgot_container[% qs_suffix %]">
<span class="separator">| </span>
- <a id="forgot_link[% qs_suffix %]" href="[% script_name FILTER html %]#forgot"
+ <a id="forgot_link[% qs_suffix %]" href="[% script_url FILTER html %]#forgot"
onclick="return show_forgot_form('[% qs_suffix %]')">Forgot Password</a>
<form action="token.cgi" method="post" id="forgot_form[% qs_suffix %]"
class="mini_forgot bz_default_hidden">
<input id="forgot_button[% qs_suffix %]" value="Reset Password"
type="submit">
<input type="hidden" name="a" value="reqpw">
+ <input type="hidden" id="token[% qs_suffix FILTER html %]" name="token" value="[% issue_hash_token(['reqpw']) FILTER html %]">
<a href="#" onclick="return hide_forgot_form('[% qs_suffix %]')">[x]</a>
</form>
</li>
enter your login name below and submit a request
to change your password.<br>
<input size="35" name="loginname">
+ <input type="hidden" id="token" name="token" value="[% issue_hash_token(['reqpw']) FILTER html %]">
<input type="submit" id="request" value="Reset Password">
</form>
[% END %]
<form id="flagtype_properties" method="post" action="editflagtypes.cgi">
<input type="hidden" name="action" value="[% action FILTER html %]">
<input type="hidden" name="can_fully_edit" value="[% can_fully_edit FILTER html %]">
- <input type="hidden" name="id" value="[% type.id %]">
+ <input type="hidden" name="id" value="[% type.id FILTER html %]">
<input type="hidden" name="token" value="[% token FILTER html %]">
<input type="hidden" name="target_type" value="[% type.target_type FILTER html %]">
<input type="hidden" name="check_clusions" value="[% check_clusions FILTER none %]">
this type will be sorted when displayed to users in a list; ignore if you
don't care what order the types appear in or if you want them to appear
in alphabetical order.<br>
- <input type="text" name="sortkey" value="[% type.sortkey || 1 %]" size="5" maxlength="5"
- [%- ' disabled="disabled"' UNLESS can_fully_edit %]>
+ <input type="text" name="sortkey" value="[% type.sortkey || 1 FILTER html %]" size="5"
+ maxlength="5" [% ' disabled="disabled"' UNLESS can_fully_edit %]>
</td>
</tr>
[% ELSE %]
<div class="contribute"><strong>Note:</strong>
- [%+ terms.Bugzilla %] is developed entirely by volunteers. The
- best way to give back to the [% terms.Bugzilla %] project is
- to <a href="http://www.bugzilla.org/contribute/">contribute</a>
- yourself! You don't have to be a programmer to contribute, there are
- lots of things that we need.
+ B[% %]ugzilla is developed entirely by volunteers.
+ The best way to give back to the B[% %]ugzilla project is to
+ <a href="http://www.bugzilla.org/contribute/">contribute</a>
+ yourself!
+ You don't have to be a programmer to contribute, there are lots of
+ things that we need.
</div>
<p>
[% sort_order = "oldest_to_newest" %]
[% END %]
-
-[%# Set up the variables as needed, depending on the sort order %]
-[% IF sort_order == "oldest_to_newest" %]
- [% count = 0 %]
- [% description = 0 %]
- [% increment = 1 %]
-[% ELSE %]
- [% increment = -1 %]
- [% IF sort_order == "newest_to_oldest" %]
- [% count = comments.size - 1 %]
- [% description = 0 %]
- [% ELSIF sort_order == "newest_to_oldest_desc_first" %]
- [% count = comments.size %]
- [% description = comments.size %]
- [% END %]
-[% END %]
-
<!-- This auto-sizes the comments and positions the collapse/expand links
to the right. -->
<table class="bz_comment_table" cellpadding="0" cellspacing="0"><tr>
<td>
[% FOREACH comment = comments %]
- [% IF count >= start_at %]
+ [% IF comment.count >= start_at %]
[% PROCESS a_comment %]
[% END %]
-
- [% count = count + increment %]
[% END %]
[% IF user.settings.comment_box_position.value == "before_comments" && user.id %]
[% comment_text = comment.body_full %]
[% RETURN IF comment_text == '' AND (comment.work_time - 0) != 0 AND !user.is_timetracker %]
- <div id="c[% count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
- [% " bz_comment_hilite" IF marks.$count %]
- [% " bz_first_comment" IF count == description %]">
- [% IF count == description %]
+ <div id="c[% comment.count %]" class="bz_comment[% " bz_private" IF comment.is_private %]
+ [% " bz_comment_hilite" IF marks.${comment.count} %]
+ [% " bz_first_comment" IF comment.count == 0 %]">
+ [% IF comment.count == 0 %]
[% class_name = "bz_first_comment_head" %]
[% comment_label = "Description" %]
[% ELSE %]
[% class_name = "bz_comment_head" %]
- [% comment_label = "Comment " _ count %]
+ [% comment_label = "Comment " _ comment.count %]
[% END %]
<div class="[% class_name FILTER html %]">
[% IF mode == "edit" %]
<span class="bz_comment_actions">
<script type="text/javascript"><!--
- addReplyLink([% count %], [% comment.id %]);
- addCollapseLink([% count %], 'Toggle comment display'); // -->
+ addReplyLink([% comment.count %], [% comment.id %]);
+ addCollapseLink([% comment.count %], 'Toggle comment display'); // -->
</script>
</span>
[% END %]
<input type="checkbox"
name="isprivate_[% comment.id %]" value="1"
id="isprivate_[% comment.id %]"
- onClick="updateCommentPrivacy(this, [% count %])"
+ onClick="updateCommentPrivacy(this, [% comment.count %])"
[% " checked=\"checked\"" IF comment.is_private %]>
<label for="isprivate_[% comment.id %]">Private</label>
</div>
<span class="bz_comment_number">
<a
- href="show_bug.cgi?id=[% bug.bug_id %]#c[% count %]">
+ href="show_bug.cgi?id=[% bug.bug_id %]#c[% comment.count %]">
[%- comment_label FILTER html %]</a>
</span>
# generated HTML
#%]
<pre class="bz_comment_text"
- [% ' id="comment_text_' _ count _ '"' IF mode == "edit" %]>
+ [% ' id="comment_text_' _ comment.count _ '"' IF mode == "edit" %]>
[%- comment_text FILTER quoteUrls(bug, comment) -%]
</pre>
</div>
bug = default, field = bug_fields.op_sys, editable = 1,
value = default.op_sys %]
</tr>
- [% IF !Param('defaultplatform') || !Param('defaultopsys') %]
+ [% IF (!Param('defaultplatform') || !Param('defaultopsys')) && !cloned_bug_id %]
<tr>
<th colspan="3"> </th>
<td id="os_guess_note" class="comment">
[% END %]
</h3>
[% IF ids.size %]
- ([% IF maxdepth -%]Up to [% maxdepth %] level[% "s" IF maxdepth > 1 %] deep | [% END -%]
- <a href="buglist.cgi?bug_id=[% ids.join(",") %]">view as [% terms.bug %] list</a>
- [% IF user.in_group('editbugs') && ids.size > 1 %]
- | <a href="buglist.cgi?bug_id=[% ids.join(",") %]&tweak=1">change several</a>
- [% END %])
+ [%# 27 chars is the length of buglist.cgi?tweak=&bug_id=" %]
+ [% use_post = (ids.join(",").length > constants.CGI_URI_LIMIT - 27 ) ? 1 : 0 %]
+ [% IF use_post %]
+ <form action="buglist.cgi" method="post">
+ <input type="hidden" name="bug_id" value="[% ids.join(",") %]">
+ [% END %]
+
+ [% IF maxdepth -%]Up to [% maxdepth %] level[% "s" IF maxdepth > 1 %] deep | [% END -%]
+ [% IF use_post %]
+ <button>view as [% terms.bug %] list</button>
+ [% IF user.in_group('editbugs') && ids.size > 1 %]
+ | <button type="submit" name="tweak" value="1">change several</button>
+ [% END %]
+ </form>
+ [% ELSE %]
+ <a href="buglist.cgi?bug_id=[% ids.join(",") %]">view as [% terms.bug %] list</a>
+ [% IF user.in_group('editbugs') && ids.size > 1 %]
+ | <a href="buglist.cgi?bug_id=[% ids.join(",") %]&tweak=1">change several</a>
+ [% END %]
+ [% END %]
+
<ul class="tree">
[% INCLUDE display_tree tree=$tree_name %]
</ul>
[% PROCESS bug/time.html.tmpl %]
- <script type="text/javascript">
- <!--
-
+<script type="text/javascript">
+<!--
/* Outputs a link to call replyToComment(); used to reduce HTML output */
function addReplyLink(id, real_id) {
/* XXX this should really be updated to use the DOM Core's
[% END %]
+[% IF user.id %]
/* Index all classifications so we can keep track of the classification
* for the selected product, which could control field visibility.
*/
all_classifications['[% product.name FILTER js %]'] = '
[%- product.classification.name FILTER js %]';
[%- END %]
-
- //-->
- </script>
+[% END %]
+//-->
+</script>
<form name="changeform" id="changeform" method="post" action="process_bug.cgi">
#%]
[% FOREACH controlled_field = field.controls_visibility_of %]
+ [% vis_names = [] %]
+ [% FOREACH visibility_value = controlled_field.visibility_values %]
+ [%# Exclude non-enterable products and components outside the current product. %]
+ [% NEXT IF field.name == "product"
+ && visibility_value.id != product.id
+ && !user.can_enter_product(visibility_value) %]
+ [% NEXT IF field.name == "component" && visibility_value.product_id != product.id %]
+ [% vis_names.push(visibility_value.name) %]
+ [% END %]
+
+ [% NEXT UNLESS vis_names.size %]
+
showFieldWhen('[% controlled_field.name FILTER js %]',
'[% field.name FILTER js %]', [
- [%- FOREACH visibility_value = controlled_field.visibility_values -%]
- '[%- visibility_value.name FILTER js -%]'[% "," UNLESS loop.last %]
- [%- END %]
+ [%~ FOREACH vis_name = vis_names ~%]
+ '[% vis_name FILTER js %]'[% "," UNLESS loop.last %]
+ [%~ END ~%]
]);
[% END %]
[% FOREACH controlled_field = legal_value.controlled_values.keys %]
[% SET cont_ids = [] %]
[% FOREACH val = legal_value.controlled_values.$controlled_field %]
+ [% NEXT IF !val.is_active %]
[% cont_ids.push(val.id) %]
[% END %]
[% NEXT IF !cont_ids.size %]
keywords =>
"You can add keywords from a defined list to $terms.bugs, in order"
- _ " to tag and group them.",
+ _ " to easily identify and group them.",
longdesc =>
"$terms.Bugs have comments added to them by $terms.Bugzilla users."
[%# We use "FILTER none" here because link_title is filtered down below. %]
[% link_title = BLOCK %]
[% display_value('bug_status', bug.bug_status) FILTER none %]
- [%+ display_value('resolution', bug.resolution) FILTER none %]
+ [% IF bug.resolution %]
+ [%+ display_value('resolution', bug.resolution) FILTER none %]
+ [% END %]
[% END %]
[% IF user.can_see_bug(bug) %]
[% FOREACH comment = new_comments.reverse %]
<div>
[% IF comment.count %]
- <b>[% "Comment # ${comment.count}" FILTER bug_link( bug,
- {comment_num => comment.count, full_url => 1}) FILTER none %]
+ <b>[% "Comment # ${comment.count}" FILTER bug_link(bug,
+ {comment_num => comment.count, full_url => 1, user => to_user}) FILTER none %]
+ on [% "$terms.bug $bug.id" FILTER bug_link(bug, { full_url => 1, user => to_user }) FILTER none %]
from [% INCLUDE global/user.html.tmpl who = comment.author %]</b>
[% END %]
- <pre>[% comment.body_full({ wrap => 1 }) FILTER quoteUrls(bug, comment) %]</pre>
+ <pre>[% comment.body_full({ wrap => 1 }) FILTER quoteUrls(bug, comment, to_user) %]</pre>
</div>
[% END %]
</p>
[% SET in_table = 0 %]
[% END %]
[% IF change.blocker %]
- [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, full_url => 1) FILTER none %] depends
- on [% "${terms.bug} ${change.blocker.id}"
- FILTER bug_link(change.blocker, full_url => 1) FILTER none %],
+ [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, {full_url => 1, user => to_user}) FILTER none %]
+ depends on
+ [%+ "${terms.bug} ${change.blocker.id}"
+ FILTER bug_link(change.blocker, {full_url => 1, user => to_user}) FILTER none %],
which changed state.
[% ELSE %]
- [% INCLUDE global/user.html.tmpl who = change.who %]
- changed [% "${terms.Bug} ${bug.id}" FILTER bug_link(bug, full_url => 1) FILTER none %]
+ [% INCLUDE global/user.html.tmpl who = change.who %] changed
+ [%+ "${terms.bug} ${bug.id}" FILTER bug_link(bug, {full_url => 1, user => to_user}) FILTER none %]
[% END %]
<br>
[% IF in_table == 0 %]
<th>[% field_label FILTER html %]</th>
<td>
[% IF change.field_name == "bug_id" %]
- [% new_value FILTER bug_link(bug, full_url => 1) FILTER none %]
+ [% new_value FILTER bug_link(bug, {full_url => 1, user => to_user}) FILTER none %]
[% ELSE %]
[% new_value FILTER html %]
[% END %]
'other_format.name',
'sizeurl',
'switchbase',
- 'format',
'cumulate',
],
'list/table.html.tmpl' => [
'tableheader',
'bug.bug_id',
- 'abbrev.$id.title || field_descs.$id || column.title',
],
'list/list.csv.tmpl' => [
'global/confirm-user-match.html.tmpl' => [
'script',
- 'fields.${field_name}.flag_type.name',
],
'global/site-navigation.html.tmpl' => [
'bug/comments.html.tmpl' => [
'comment.id',
+ 'comment.count',
'bug.bug_id',
],
],
'admin/flag-type/edit.html.tmpl' => [
- 'type.id',
- 'type.sortkey || 1',
'selname',
],
setting in [% constants.bz_locations.localconfig FILTER html %].
[% ELSIF error == "mismatched_bug_ids_on_obsolete" %]
- Attachment [% attach_id FILTER html %] ([% description FILTER html %])
- is attached to [% terms.bug %] [%+ attach_bug_id FILTER html %],
+ Attachment [% attach_id FILTER html %] is attached to another [% terms.bug %],
but you tried to flag it as obsolete while creating a new attachment to
[%+ terms.bug %] [%+ my_bug_id FILTER html %].
[% ELSIF error == "token_generation_error" %]
Something is seriously wrong with the token generation system.
+ [% ELSIF error == "cancel_token_does_not_exist" %]
+ The token to be cancelled does not exist.
+
[% ELSIF error == "template_error" %]
[% template_error_msg FILTER html %]
[% ELSIF error == "invalid_post_bug_submit_action" %]
Invalid setting for post_bug_submit_action
+ [% ELSIF error == "search_field_operator_unsupported" %]
+ [% terms.Bugzilla %] does not support the search type
+ "[% operator FILTER html %]".
+
[% ELSE %]
[%# Try to find hooked error messages %]
[% error_message = Hook.process("errors") %]
[% ELSE %]
matched
<b>[% query.value.users.0.identity FILTER html %]</b>
- <input type="hidden" name="[% field.key FILTER html %]"
- value="[% query.value.users.0.login FILTER html %]">
[% END %]
[% ELSE %]
[% IF (query.key.length < 3) && !Param('emailsuffix') %]
[% IF matchsuccess == 1 %]
- [% SET exclude_these =
- matches.keys.merge(['Bugzilla_login', 'Bugzilla_password']) %]
+ [% SET exclude_these = ['Bugzilla_login', 'Bugzilla_password'] %]
+ [% FOREACH key IN matches.keys %]
+ [% exclude_these.push(key) IF cgi.param(key) == '' %]
+ [% END %]
[% SET exclude = '^' _ exclude_these.join('|') _ '$' %]
[% PROCESS "global/hidden-fields.html.tmpl" exclude = exclude %]
[% ELSIF field_labels.$field_name %]
[% field_labels.$field_name FILTER html %]
[% ELSIF field_name.match("^requestee") %]
- [% fields.${field_name}.flag_type.name %] requestee
+ [% fields.${field_name}.flag_type.name FILTER html %] requestee
[% ELSE %]
[% field_name FILTER html %]
[% END %]
"setting" => "Setting",
"settings" => "Settings",
"short_desc" => "Summary",
+ "short_short_desc" => "Summary",
"status_whiteboard" => "Whiteboard",
"tag.name" => "Tags",
"target_milestone" => "Target Milestone",
#%]
<body onload="[% onload %]"
- class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') %]
+ class="[% urlbase.replace('^https?://','').replace('/$','').replace('[-~@:/.]+','-') FILTER css_class_quote %]
[% FOREACH class = bodyclasses %]
[% ' ' %][% class FILTER css_class_quote %]
[% END %] yui-skin-sam">
[% title = "Missing Search" %]
[% docslinks = {'query.html' => "Searching for $terms.bugs",
'query.html#list' => "$terms.Bug lists"} %]
- The search named <em>[% queryname FILTER html %]</em>
+ The search named <em>[% name FILTER html %]</em>
[% IF sharer_id && sharer_id != user.id %]
has not been made visible to you.
[% ELSE %]
[%+ constants.USER_PASSWORD_MIN_LENGTH FILTER html %] characters long.
[% ELSIF error == "password_not_complex" %]
- [% title = "Password Fails Requirements" %]
- [% passregex = Param('password_complexity') %]
- The password must contain at least one:
- <ul>
- [% IF passregex.search('letters') %]
- <li>UPPERCASE letter</li>
- <li>lowercase letter</li>
- [% END %]
- [% IF passregex.search('numbers') %]
- <li>digit</li>
- [% END %]
- [% IF passregex.search('specialchars') %]
- <li>special character</li>
- [% END %]
- </ul>
+ [% title = "Password Fails Requirements" %]
+ [% passregex = Param('password_complexity') %]
+ The password must contain at least one:
+ <ul>
+ [% IF passregex == 'letters_numbers_specialchars' %]
+ <li>letter</li>
+ <li>special character</li>
+ [% ELSIF passregex.search('letters') %]
+ <li>UPPERCASE letter</li>
+ <li>lowercase letter</li>
+ [% END %]
+ [% IF passregex.search('numbers') %]
+ <li>digit</li>
+ [% END %]
+ </ul>
[% ELSIF error == "product_access_denied" %]
[% title = "Product Access Denied" %]
[% title = "Unknown Tab" %]
<code>[% current_tab_name FILTER html %]</code> is not a legal tab name.
+ [% ELSIF error == "value_inactive" %]
+ [% title = "Value is Not Active" %]
+ [% type = BLOCK %][% INCLUDE object_name class = class %][% END %]
+ The [% type FILTER html %] value '[% value FILTER html %]' is not active.
+
[% ELSIF error == "version_already_exists" %]
[% title = "Version Already Exists" %]
[% admindocslinks = {'versions.html' => 'Administering versions'} %]
[% FOREACH q = Bugzilla.user.queries %]
[% IF q.name == namedcmd %]
- or <a href="query.cgi?[% q.url FILTER uri %]">edit</a>
+ or <a href="query.cgi?[% q.url FILTER html %]">edit</a>
[% END %]
[% END %]
<h1 style="margin-top: 20%; text-align: center;">Please stand by ...</h1>
[% IF debug %]
- <p>
- [% FOREACH debugline = debugdata %]
- <code>[% debugline FILTER html %]</code><br>
- [% END %]
- </p>
- <p>
- <code>[% query FILTER html %]</code>
- </p>
+ <p>[% query FILTER html %]</p>
+ [% IF query_explain.defined %]
+ <pre>[% query_explain FILTER html %]</pre>
+ [% END %]
[% END %]
-
</body>
</html>
[% PROCESS new_order %]
[%-#%]&query_based_on=
[% defaultsavename OR searchname FILTER uri %]">
- [%- abbrev.$id.title || field_descs.$id || column.title -%]
+ [%- abbrev.$id.title || field_descs.$id || column.title FILTER html -%]
[% PROCESS order_arrow ~%]
</a>
</th>
<h2 id="v42_point">Updates in this 4.2.x Release</h2>
+<h3>4.2.7</h3>
+
+<p>This release fixes several security issues. See the
+ <a href="http://www.bugzilla.org/security/4.0.10/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following [% terms.bugs %] have been fixed in this release:</p>
+
+<ul>
+ <li>Internet Explorer 11 and KHTML-based browsers such as Konqueror can now
+ display buglists correctly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=902515">[% terms.Bug %] 902515</a> and
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=914262">[% terms.bug %] 914262</a>)</li>
+ <li>When the <kbd>password_complexity</kbd> parameter was set to
+ 'letters_numbers_specialchars', passwords containing numbers and special
+ characters only were accepted. Now it makes sure that a letter is also present.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=897264">[% terms.Bug %] 897264</a>)</li>
+ <li>With DB servers doing case-insensitive comparisons, such as MySQL, tokens
+ and login cookies were not correctly validated as the case was ignored.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=906745">[% terms.Bug %] 906745</a> and
+ <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=907438">[% terms.bug %] 907438</a>)</li>
+ <li>All security headers (such as X-Frame-Options) are now returned when using XML-RPC.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=787328">[% terms.Bug %] 787328</a>)</li>
+ <li>Oracle crashed when reporting a new [% terms.bug %] if a custom free-text field
+ was non-mandatory and left empty.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=919475">[% terms.Bug %] 919475</a>)</li>
+ <li>It was not possible to import [% terms.bugs %] using <kbd>importxml.pl</kbd> with Oracle.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=848063">[% terms.Bug %] 848063</a>)</li>
+</ul>
+
+<h3>4.2.6</h3>
+
+<p>The following important fixes/changes have been made in this release:</p>
+
+<ul>
+ <li>MySQL 5.6 is now supported.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=852560">[% terms.Bug %] 852560</a>)</li>
+ <li>A regression introduced in [% terms.Bugzilla %] 4.2.4 made Oracle crash
+ when installing [% terms.Bugzilla %] for the first time.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=858911">[% terms.Bug %] 858911</a>)</li>
+ <li>If a custom field depends on a product, component or classification,
+ the "mandatory" bit was ignored on [% terms.bug %] creation.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=782210">[% terms.Bug %] 782210</a>)</li>
+ <li>Queries involving flags were broken in several ways. These queries
+ have been fixed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=828344">[% terms.Bug %] 828344</a>)</li>
+ <li>Tabular reports involving the empty resolution did not link [% terms.bug %]
+ counts correctly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=212471">[% terms.Bug %] 212471</a>)</li>
+ <li>The <kbd>B[%%]ug.search</kbd> WebService method was returning all visible
+ [%+ terms.bugs %] when called with no arguments, ignoring the
+ <kbd>max_search_results</kbd> and <kbd>search_allow_no_criteria</kbd> parameters.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=859118">[% terms.Bug %] 859118</a>)</li>
+</ul>
+
+<h3>4.2.5</h3>
+
+<p>This release fixes one security issue. See the
+ <a href="http://www.bugzilla.org/security/3.6.12/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+ release:</p>
+
+<ul>
+ <li>Queries involving commenters were slow to return results. These queries
+ have been optimized for better performance.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=818007">[% terms.Bug %] 818007</a>)</li>
+ <li>It is no longer possible to create a new [% terms.bug %] using a disabled
+ component, target milestone or version. These inactive values are also no
+ longer accessible when moving [% terms.abug %] into another product.
+ ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=752946">752946</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=840824">840824</a>)</li>
+ <li>It was possible to create a new [% terms.bug %] with no description
+ despite the status workflow required one for new [% terms.bugs %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=818890">[% terms.Bug %] 818890</a>)</li>
+ <li>Custom multi-select fields are now available in the "Search By Change
+ History" section of the "Advanced Search" page.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=839950">[% terms.Bug %] 839950</a>)</li>
+ <li>A custom select field could have its list of values truncated if one
+ or more of its values were disabled and the visibility of the values were
+ controlled by another field.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=806809">[% terms.Bug %] 806809</a>)</li>
+ <li>Warnings thrown by Return::Value 1.666002 about this deprecated module
+ and which are polluting the web server error log are now disabled.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=826678">[% terms.Bug %] 826678</a>)</li>
+</ul>
+
+<h3>4.2.4</h3>
+
+<p>This release fixes several security issues. See the
+ <a href="http://www.bugzilla.org/security/3.6.11/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+ release:</p>
+
+<ul>
+ <li>Queries involving group substitution were crashing when the "usevisibilitygroups"
+ parameter was enabled. Also, CVE-2011-2979 was not fully fixed in
+ [%+ terms.Bugzilla %] 4.1.3.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=788098">[% terms.Bug %] 788098</a>)</li>
+ <li>Flag names were not properly escaped when displayed on the "confirm user
+ match" page. An admin could unintentionally break the display of this page
+ if a flag name contains a < or > character, because these characters
+ were not filtered.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=790215">[% terms.Bug %] 790215</a>)</li>
+ <li>We now prevent private WebServices methods from being called by external
+ applications.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=793826">[% terms.Bug %] 793826</a>)</li>
+ <li>PostgreSQL 9.2 requires DBD::Pg 2.19.3.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=799721">[% terms.Bug %] 799721</a>)</li>
+ <li>Oracle was crashing when listing keywords or flags in buglists.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780053">[% terms.Bug %] 780053</a>)</li>
+ <li>Oracle was crashing when typing several bare words in the QuickSearch field.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=804505">[% terms.Bug %] 804505</a>)</li>
+ <li>[% terms.Bugs %] with the resolution MOVED couldn't be edited anymore.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=757935">[% terms.Bug %] 757935</a>)</li>
+ <li>Editing dependencies from the "Change Several [% terms.Bugs %] at Once"
+ page didn't work as expected. [% terms.Bug %] IDs were incorrectly parsed.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=790909">[% terms.Bug %] 790909</a>)</li>
+ <li>The "Actual Hours" axis now works correctly in tabular and graphical reports.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=794389">[% terms.Bug %] 794389</a>)</li>
+ <li><kbd>checksetup.pl</kbd> was failing to run if the Voting extension was
+ enabled on a fresh installation and some mandatory modules were missing.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652047">[% terms.Bug %] 652047</a>)</li>
+ <li>[% terms.Bugzilla %] no longer crashes when viewing [% terms.abug %] while
+ a custom field is being added.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=531243">[% terms.Bug %] 531243</a>)</li>
+ <li>For improved security, we now send the "X-Content-Type-Options: nosniff"
+ and "X-XSS-Protection: block" headers with every response.
+ ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=671612">671612</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=680771">680771</a>)</li>
+</ul>
+
+<h3>4.2.3</h3>
+
+<p>This release fixes two security issues. See the
+ <a href="http://www.bugzilla.org/security/3.6.10/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+ release:</p>
+
+<ul>
+ <li>Attaching a file to [% terms.abug %] was broken due to a change in
+ Perl 5.16.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=771100">[% terms.Bug %] 771100</a>)</li>
+ <li>A regression in [% terms.Bugzilla %] 4.2.2 made Oracle crash when
+ displaying a buglist.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780028">[% terms.Bug %] 780028</a>)</li>
+ <li>It was possible to search on history for comments and attachments you
+ cannot see (though these private comments and attachments are never disclosed).
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=779709">[% terms.Bug %] 779709</a>)</li>
+ <li>PostgreSQL databases could be created with the wrong encoding despite
+ the utf8 parameter being enabled.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=783786">[% terms.Bug %] 783786</a>)</li>
+ <li>Scheduled whines could be sent at the wrong time on Oracle.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=559539">[% terms.Bug %] 559539</a>)</li>
+ <li>Tokens are no longer included in saved queries.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=772953">[% terms.Bug %] 772953</a>)</li>
+ <li>An admin could unintentionally break the display of buglists if a custom
+ field description contains a < or > character, because these characters
+ were not filtered.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=785917">[% terms.Bug %] 785917</a>)</li>
+ <li>Adding or removing a DB column in Oracle didn't handle SERIAL columns
+ correctly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=731156">[% terms.Bug %] 731156</a>)</li>
+ <li>A minor CSRF vulnerability in token.cgi allowed possible unauthorized
+ password reset e-mail requests.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=706271">[% terms.Bug %] 706271</a>)</li>
+</ul>
+
+<h3>4.2.2</h3>
+
+<p>This release fixes two security issues. See the
+ <a href="http://www.bugzilla.org/security/3.6.9/">Security Advisory</a>
+ for details.</p>
+
+<p>In addition, the following important fixes/changes have been made in this
+ release:</p>
+
+<ul>
+ <li>A regression introduced in [% terms.Bugzilla %] 4.0 caused some login
+ names to be ignored when entered in the CC list of [% terms.bugs %].
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=756314">[% terms.Bug %] 756314</a>)</li>
+ <li>Some queries could trigger an invalid SQL query if strings entered by
+ the user contained leading or trailing whitespaces.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=760075">[% terms.Bug %] 760075</a>)</li>
+ <li>The auto-completion form for keywords no longer automatically selects
+ the first keyword in the list when the field is empty.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=764517">[% terms.Bug %] 764517</a>)</li>
+ <li>A regression in [% terms.Bugzilla %] 4.2 prevented classifications
+ from being used in graphical and tabular reports in the "Multiple Tables"
+ field.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=753688">[% terms.Bug %] 753688</a>)</li>
+ <li>Attachments created by the <kbd>email_in.pl</kbd> script were associated
+ to the wrong comment.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=762785">[% terms.Bug %] 762785</a>)</li>
+ <li>Very long dependency lists can now be viewed correctly.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=762783">[% terms.Bug %] 762783</a>)</li>
+ <li>Keywords are now correctly escaped in the auto-completion form to prevent
+ any XSS abuse.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=754561">[% terms.Bug %] 754561</a>)</li>
+ <li>A regression introduced in [% terms.Bugzilla %] 4.0rc2 when fixing
+ CVE-2011-0046 caused the "Un-forget the search" link to not work correctly
+ anymore when restoring a deleted saved search, because this link was
+ lacking a valid token.
+ (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=768870">[% terms.Bug %] 768870</a>)</li>
+ <li>Two minor CSRF vulnerabilities have been fixed which could let an attacker
+ alter your default search criteria in the Advanced Search page.
+ ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=754672">754672</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=754673">754673</a>)</li>
+</ul>
+
<h3>4.2.1</h3>
<p>This release fixes two security issues. See the
[%- terms.Bug %] 584742</a>: When viewing [% terms.abug %], WebKit-based
browsers can automatically reset a field's selected value when the field
has disabled values.</li>
+ <li><a href="https://bugzilla.mozilla.org/show_bug.cgi?id=780053">
+ [%- terms.Bug %] 780053</a>: Oracle crashes when listing keywords, tags
+ or flags in buglists.</li>
</ul>
(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=640719">[% terms.Bug %] 640719</a>)</li>
<li>Email notifications about dependencies and flags had the wrong
timestamp.
- (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=643910">[% terms.Bug %] 643910</a>
- and (<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652165">[% terms.Bug %] 652165</a>)</li>
+ ([% terms.Bugs %] <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=643910">643910</a>
+ and <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=652165">652165</a>)</li>
<li>You can now select "UTC" as a valid timezone in General Preferences.
(<a href="https://bugzilla.mozilla.org/show_bug.cgi?id=646209">[% terms.Bug %] 646209</a>)</li>
<li>Automatic duplicate detection now works on PostgreSQL (although
[% END %]
<script type="text/javascript">
+function bz_encode (str, decode) {
+ // First decode HTML entities, if requested.
+ if (decode)
+ str = str.replace(/</g, "<").replace(/>/g, ">").replace(/"/g, '"')
+ .replace(/ /g, " ").replace(/&/g, "&").replace(/\s+$/,"");
+
+ // encodeURIComponent() doesn't escape single quotes.
+ return encodeURIComponent(str).replace(/'/g, escape);
+};
+
YAHOO.util.Event.addListener(window, "load", function() {
this.Linkify = function(elLiner, oRecord, oColumn, oData) {
if (oData == 0)
elLiner.innerHTML = ".";
else if (oRecord.getData("row_title") == "Total")
- elLiner.innerHTML = "<a href='[% urlbase %]&[% col_field FILTER js %]="
- + oColumn.field + "[% '&' _ row_vals IF row_vals %]'>"
- + oData + "</a>";
+ elLiner.innerHTML = '<a href="[% urlbase FILTER js %]&[% col_field FILTER uri FILTER js %]='
+ + bz_encode(oColumn.field)
+ + '[% "&" _ row_vals IF row_vals %]">' + oData + '</a>';
else
- elLiner.innerHTML = "<a href='[% urlbase %]&[% row_field FILTER js %]="
- + oRecord.getData("row_title").replace(/\s+$/,"")
- + "&[% col_field FILTER js %]=" + oColumn.field
- + "'>" + oData + "</a>";
+ elLiner.innerHTML = '<a href="[% urlbase FILTER js %]&[% row_field FILTER uri FILTER js %]='
+ + bz_encode(oRecord.getData("row_title"), 1)
+ + '&[% col_field FILTER uri FILTER js %]='
+ + bz_encode(oColumn.field) + '">' + oData + '</a>';
};
this.LinkifyTotal = function(elLiner, oRecord, oColumn, oData) {
if (oData == 0)
elLiner.innerHTML = ".";
else if (oRecord.getData("row_title") == "Total")
- elLiner.innerHTML = "<a href='[% urlbase %][% '&' _ row_vals IF row_vals %]
- [%~ '&' _ col_vals IF col_vals %]'>"
- + oData + "</a>";
+ elLiner.innerHTML = '<a href="[% urlbase FILTER js %][% "&" _ row_vals IF row_vals %]
+ [%~ "&" _ col_vals IF col_vals %]">'
+ + oData + '</a>';
else
- elLiner.innerHTML = "<a href='[% urlbase %]&[% row_field FILTER js %]="
- + oRecord.getData("row_title").replace(/\s+$/,"")
- + "[% '&' _ col_vals IF col_vals %]'>" + oData + "</a>";
+ elLiner.innerHTML = '<a href="[% urlbase FILTER js %]&[% row_field FILTER uri FILTER js %]='
+ + bz_encode(oRecord.getData("row_title"), 1)
+ + '[% "&" _ col_vals IF col_vals %]">' + oData + '</a>';
YAHOO.util.Dom.addClass(elLiner.parentNode, "ttotal");
};
var myColumnDefs = [
{key:"row_title", label:"", sortable:true, sortOptions: { sortFunction:totalNumberSorter }},
[% FOREACH col = col_names %]
- {key:"[% col FILTER js %]", label:"[% display_value(col_field, col) FILTER js %]", sortable:true,
+ {key:"[% col FILTER js %]", label:"[% display_value(col_field, col) FILTER html FILTER js %]", sortable:true,
formatter:this.Linkify, sortOptions: { defaultDir: YAHOO.widget.DataTable.CLASS_DESC, sortFunction:totalNumberSorter }},
[% END %]
{key:"total", label:"Total", sortable:true, formatter:this.LinkifyTotal,
[% col_idx = 0 %]
[% row_idx = 0 %]
[% grand_total = 0 %]
-<div id="tabular_report_container_[% tbl FILTER js %]">
+<div id="tabular_report_container_[% tbl FILTER html %]">
<table id="tabular_report" border="1">
[% IF col_field %]
<thead>
this report</a>
[% ELSE %]
<a href="query.cgi?[% switchbase %]&chart_format=
- [% format %]&format=report-graph&cumulate=[% cumulate %]">
+ [% format FILTER uri %]&format=report-graph&cumulate=[% cumulate %]">
Edit this report
</a>
[% END %]
[% " selected" IF default.emailtype.$n == qv.name %]>[% qv.description %]</option>
[% END %]
</select>
- [% IF feature_enabled('jsonrpc') %]
+ [% IF feature_enabled('jsonrpc') && Param('ajax_user_autocompletion') %]
<div id="email[% n %]_autocomplete">
[% END %]
<input name="email[% n %]" class="email" id="email[% n %]"
value="[% default.email.$n FILTER html %]">
- [% IF feature_enabled('jsonrpc') %]
+ [% IF feature_enabled('jsonrpc') && Param('ajax_user_autocompletion') %]
<div id="email[% n %]_autocomplete_container"></div>
</div>
<script type="text/javascript">
"Last Changed" => "Last Changed" } %]
<input type="hidden" name="cmdtype" value="doit">
+[% IF user.id %]
+ <input type="hidden" name="token" value="[% issue_hash_token(['searchknob']) FILTER html %]">
+[% END %]
<p>
<label for="order">Sort results by</label>:
<input type="submit" id="[% button_name FILTER html %]"
value="[% button_name FILTER html %]">
[% IF known_name %]
- [%# We store known_name in case the user add a boolean chart. %]
+ [%# We store known_name in case the user adds a boolean chart. %]
<input type="hidden" name="known_name" value="[% known_name FILTER html %]">
[%# The name of the existing query will be passed to buglist.cgi. %]
[% END %]
</p>
-<p>
-
- <input type="checkbox" id="remasdefault"
- name="remtype" value="asdefault">
- <label for="remasdefault">
- and remember these as my default search options
- </label>
-</p>
+[% IF user.id %]
+ <p>
+
+ <input type="checkbox" id="remasdefault"
+ name="remtype" value="asdefault">
+ <label for="remasdefault">
+ and remember these as my default search options
+ </label>
+ </p>
+[% END %]
[% IF userdefaultquery %]
<p>
- <a href="query.cgi?nukedefaultquery=1">
+ <a href="query.cgi?nukedefaultquery=1&token=
+ [%- issue_hash_token(['nukedefaultquery']) FILTER uri %]">
Set my default search back to the system default</a>.
</p>
[% END %]
[% js_data = BLOCK %]
-var queryform = "queryform"
+var queryform = "queryform";
+function remove_token() {
+ if (queryform.token) {
+ var asDefault = document.getElementById('remasdefault');
+ queryform.token.disabled = !asDefault.checked;
+ }
+}
[% END %]
[% PROCESS global/header.html.tmpl
<p id="search_help">Hover your mouse over each field label to get help for that field.</p>
-<form method="post" action="buglist.cgi" name="queryform" id="queryform">
+<form method="post" action="buglist.cgi" name="queryform" id="queryform"
+ onsubmit="remove_token()">
[% PROCESS search/form.html.tmpl %]
[% " selected" IF default.$name.0 == field %]>
[% field_descs.$field || field FILTER html %]</option>
[% END %]
-
- [%# Single-select fields are also valid column names. %]
- [% FOREACH field = custom_fields %]
- <option value="[% field.name FILTER html %]"
- [% " selected" IF default.$name.0 == field.name %]>
- [% field.description FILTER html %]</option>
- [% END %]
</select>
[% END %]
http://cyberelk.net/tim/patchutils/
END
- ppm_repo_add => <<EOT,
-***********************************************************************
-* Note For Windows Users *
-***********************************************************************
-* In order to install the modules listed below, you first have to run *
-* the following command as an Administrator: *
-* *
-* ppm repo add theory58S ##theory_url##
-EOT
- ppm_repo_up => <<EOT,
-* *
-* Then you have to do (also as an Administrator): *
-* *
-* ppm repo up theory58S *
-* *
-* Do that last command over and over until you see "theory58S" at the *
-* top of the displayed list. *
-EOT
template_precompile => "Precompiling templates...",
template_removal_failed => <<END,
WARNING: The directory '##template_cache##' could not be removed.
trick_taint($token);
# Make sure the token exists in the database.
- my ($tokentype) = $dbh->selectrow_array('SELECT tokentype FROM tokens
- WHERE token = ?', undef, $token);
- $tokentype || ThrowUserError("token_does_not_exist");
+ my ($db_token, $tokentype) = $dbh->selectrow_array('SELECT token, tokentype FROM tokens
+ WHERE token = ?', undef, $token);
+ (defined $db_token && $db_token eq $token)
+ || ThrowUserError("token_does_not_exist");
# Make sure the token is the correct type for the action being taken.
if ( grep($action eq $_ , qw(cfmpw cxlpw chgpw)) && $tokentype ne 'password' ) {
ThrowUserError("password_change_requests_not_allowed");
}
+ # Check the hash token to make sure this user actually submitted
+ # the forgotten password form.
+ my $token = $cgi->param('token');
+ check_hash_token($token, ['reqpw']);
+
validate_email_syntax($login_name)
|| ThrowUserError('illegal_email_address', {addr => $login_name});
);
# allwords and allwordssubstr have these broken tests in common.
-#
-# allwordssubstr on cc fields matches against a single cc,
-# instead of matching against all ccs on a bug.
use constant ALLWORDS_BROKEN => (
+ # allwordssubstr on cc fields matches against a single cc,
+ # instead of matching against all ccs on a bug.
cc => { contains => [1] },
+ # bug 828344 changed how these searches operate to revert back to the 4.0
+ # behavour, so these tests need to be updated (bug 849117).
+ 'flagtypes.name' => { contains => [1] },
+ longdesc => { contains => [1] },
);
# Fields that don't generally work at all with changed* searches, but
'allwords-<1>' => {
ALLWORDS_BROKEN,
},
+ 'anywords-<1>' => {
+ 'flagtypes.name' => { contains => [1,2,3,4,5] },
+ },
+ 'anywords-<1> <2>' => {
+ 'flagtypes.name' => { contains => [3,4,5] },
+ },
+ 'anywordssubstr-<1> <2>' => {
+ 'flagtypes.name' => { contains => [3,4,5] },
+ },
# setters.login_name and requestees.login name aren't tracked individually
# in bugs_activity, so can't be searched using this method.
# This should probably search the reporter.
creation_ts => { contains => [1] },
},
+ notequals => {
+ 'flagtypes.name' => { contains => [1, 5] },
+ longdesc => { contains => [1] },
+ },
+ notregexp => {
+ 'flagtypes.name' => { contains => [1, 5] },
+ longdesc => { contains => [1] },
+ },
+ notsubstring => {
+ 'flagtypes.name' => { contains => [5] },
+ longdesc => { contains => [1] },
+ },
+ nowords => {
+ 'flagtypes.name' => { contains => [1, 5] },
+ },
+ nowordssubstr => {
+ 'flagtypes.name' => { contains => [5] },
+ },
};
###################
# These are field/operator combinations that are broken when run under NOT().
use constant BROKEN_NOT => {
- allwords => {
- cc => { contains => [1] },
+ allwords => {
+ cc => { contains => [1] },
+ 'flagtypes.name' => { contains => [1, 5] },
+ longdesc => { contains => [1] },
},
'allwords-<1> <2>' => {
cc => { },
},
allwordssubstr => {
- cc => { contains => [1] },
+ cc => { contains => [1] },
+ 'flagtypes.name' => { contains => [5, 6] },
+ longdesc => { contains => [1] },
},
'allwordssubstr-<1>,<2>' => {
- cc => { },
+ cc => { },
+ longdesc => { contains => [1] },
+ },
+ anyexact => {
+ 'flagtypes.name' => { contains => [1, 2, 5] },
+ },
+ 'anywords-<1>' => {
+ 'flagtypes.name' => { contains => [1, 2, 3, 4, 5] },
+ },
+ 'anywords-<1> <2>' => {
+ 'flagtypes.name' => { contains => [3, 4, 5] },
+ },
+ anywordssubstr => {
+ 'flagtypes.name' => { contains => [5] },
+ },
+ 'anywordssubstr-<1> <2>' => {
+ 'flagtypes.name' => { contains => [3,4,5] },
+ },
+ casesubstring => {
+ 'flagtypes.name' => { contains => [5] },
},
changedafter => {
"attach_data.thedata" => { contains => [2, 3, 4] },
dependson => { contains => [1, 3] },
work_time => { contains => [1] },
FIELD_TYPE_BUG_ID, { contains => [1 .. 4] },
-
},
changedto => {
CHANGED_BROKEN_NOT,
"remaining_time" => { contains => [1] },
},
greaterthan => {
- cc => { contains => [1] },
+ cc => { contains => [1] },
+ 'flagtypes.name' => { contains => [5] },
},
greaterthaneq => {
cc => { contains => [1] },
+ 'flagtypes.name' => { contains => [2, 5] },
+ },
+ equals => {
+ 'flagtypes.name' => { contains => [1, 5] },
+ },
+ notequals => {
+ longdesc => { contains => [1] },
+ },
+ notregexp => {
+ longdesc => { contains => [1] },
+ },
+ notsubstring => {
+ longdesc => { contains => [1] },
+ },
+ 'nowords-<1>' => {
+ 'flagtypes.name' => { contains => [5] },
+ },
+ 'nowordssubstr-<1>' => {
+ 'flagtypes.name' => { contains => [5] },
+ },
+ lessthan => {
+ 'flagtypes.name' => { contains => [5] },
+ },
+ lessthaneq => {
+ 'flagtypes.name' => { contains => [1, 5] },
+ },
+ regexp => {
+ 'flagtypes.name' => { contains => [1, 5] },
+ longdesc => { contains => [1] },
+ },
+ substring => {
+ 'flagtypes.name' => { contains => [5] },
+ longdesc => { contains => [1] },
},
};
use warnings;
use Bugzilla::Search;
use Bugzilla::Test::Search::Constants;
+use Bugzilla::Util qw(trim);
use Data::Dumper;
use Scalar::Util qw(blessed);
my $self = shift;
return $self->search_test->bug(@_);
}
+sub number {
+ my ($self, $id) = @_;
+ foreach my $number (1..NUM_BUGS) {
+ return $number if $self->search_test->bug($number)->id == $id;
+ }
+ return 0;
+}
# The name displayed for this test by Test::More. Used in test descriptions.
sub name {
return $self->{translated_value};
}
# Used in failure diagnostic messages.
-sub debug_value {
- my ($self) = @_;
- return "Value: '" . $self->translated_value . "'";
+sub debug_fail {
+ my ($self, $number, $results, $sql) = @_;
+ my @expected = @{ $self->test->{contains} };
+ my @results = sort
+ map { $self->number($_) }
+ map { $_->[0] }
+ @$results;
+ return
+ " Value: '" . $self->translated_value . "'\n" .
+ "Expected: [" . join(',', @expected) . "]\n" .
+ " Results: [" . join(',', @results) . "]\n" .
+ trim($sql) . "\n";
}
# True for a bug if we ran the "transform" function on it and the
# The tests we know are broken for this operator/field combination.
sub _known_broken {
my ($self, $constant, $skip_pg_check) = @_;
+
$constant ||= KNOWN_BROKEN;
my $field = $self->field;
my $type = $self->field_object->type;
my $value_name = "$operator-$value";
if (my $extra_name = $self->test->{extra_name}) {
$value_name .= "-$extra_name";
- }
-
+ }
+
my $value_broken = $constant->{$value_name}->{$field};
$value_broken ||= $constant->{$value_name}->{$type};
return $value_broken if $value_broken;
if ($self->bug_is_contained($number)) {
ok($result_ids{$bug_id},
"$name: contains bug $number ($bug_id)")
- or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+ or diag $self->debug_fail($number, $results, $sql);
}
else {
ok(!$result_ids{$bug_id},
"$name: does not contain bug $number ($bug_id)")
- or diag Dumper($results) . $self->debug_value . "\n\nSQL: $sql";
+ or diag $self->debug_fail($number, $results, $sql);
}
}
}