diff options
6 files changed, 124 insertions, 46 deletions
diff --git a/extensions/BugmailFilter/Extension.pm b/extensions/BugmailFilter/Extension.pm index 053b07575..035de241c 100644 --- a/extensions/BugmailFilter/Extension.pm +++ b/extensions/BugmailFilter/Extension.pm @@ -45,6 +45,9 @@ sub user_preferences { user_id => Bugzilla->user->id, }; $params->{field_name} = $input->{field} || IS_NULL; + if ($params->{field_name} eq '~') { + $params->{field_name} = '~' . $input->{field_contains}; + } $params->{relationship} = $input->{relationship} || IS_NULL; if (my $product_name = $input->{product}) { my $product = Bugzilla::Product->check({ @@ -160,6 +163,20 @@ sub user_preferences { }) } ]; + # set field_description + foreach my $filter (@{ $vars->{filters} }) { + my $field_name = $filter->field_name; + if (!$field_name) { + $filter->field_description('Any'); + } + elsif (substr($field_name, 0, 1) eq '~') { + $filter->field_description('~ ' . substr($field_name, 1)); + } + else { + $filter->field_description($fields{$field_name} || $filter->field->description); + } + } + # build a list of tracking-flags, grouped by type require Bugzilla::Extension::TrackingFlags::Constants; require Bugzilla::Extension::TrackingFlags::Flag; @@ -203,39 +220,34 @@ sub user_wants_mail { }); return unless @$filters; - my $fields = [ map { $_->{field_name} } @$diffs ]; + my $fields = [ + map { { + filter_field => $_->{field_name}, # filter's field_name + field_name => $_->{field_name}, # raw bugzilla field_name + } } + @$diffs + ]; # insert fake fields for new attachments and comments if (@$comments) { if (grep { $_->type == CMT_ATTACHMENT_CREATED } @$comments) { - push @$fields, 'attachment.created'; + push @$fields, { filter_field => 'attachment.created' }; } if (grep { $_->type != CMT_ATTACHMENT_CREATED } @$comments) { - push @$fields, 'comment.created'; + push @$fields, { filter_field => 'comment.created' }; } } - # replace tracking flag fields with fake tracking flag types + # set filter_field on tracking flags to tracking.$type require Bugzilla::Extension::TrackingFlags::Flag; - my %count; - my @tracking_flags; - foreach my $field (@$fields, Bugzilla->tracking_flag_names) { - $count{$field}++; - } - foreach my $field (keys %count) { - push @tracking_flags, $field - if $count{$field} > 1; - } - my %tracking_types = - map { $_->flag_type => 1 } - @{ Bugzilla::Extension::TrackingFlags::Flag->match({ - name => \@tracking_flags - })}; - foreach my $type (keys %tracking_types) { - push @$fields, 'tracking.' . $type; - } - foreach my $field (Bugzilla->tracking_flag_names) { - $fields = [ grep { $_ ne $field } @$fields ]; + my @tracking_flags = Bugzilla->tracking_flags; + foreach my $field (@$fields) { + next unless my $field_name = $field->{field_name}; + foreach my $tracking_flag (@tracking_flags) { + if ($field_name eq $tracking_flag->name) { + $field->{filter_field} = 'tracking.'. $tracking_flag->flag_type; + } + } } if (_should_drop($fields, $filters, $args)) { @@ -290,43 +302,42 @@ sub _should_drop { # exclusions # drop email where we are excluding all changed fields - my %exclude = map { $_ => 0 } @$fields; my $params = { product_id => $bug->product_id, component_id => $bug->component_id, rel_map => \@rel_map, }; - foreach my $field_name (@$fields) { - $params->{field_name} = $field_name; + foreach my $field (@$fields) { + $params->{field} = $field; foreach my $filter (grep { $_->is_exclude } @$filters) { if ($filter->matches($params)) { - $exclude{$field_name} = 1; + $field->{exclude} = 1; last; } } } # no need to process includes if nothing was excluded - if (!grep { $exclude{$_} } @$fields) { + if (!grep { $_->{exclude} } @$fields) { return 0; } # inclusions # flip the bit for fields that should be included - foreach my $field_name (@$fields) { - $params->{field_name} = $field_name; + foreach my $field (@$fields) { + $params->{field} = $field; foreach my $filter (grep { $_->is_include } @$filters) { if ($filter->matches($params)) { - $exclude{$field_name} = 0; + $field->{exclude} = 0; last; } } } # drop if all fields are still excluded - return !(grep { !$exclude{$_} } keys %exclude); + return !(grep { !$_->{exclude} } @$fields); } # catch when fields are renamed, and update the field_name entires diff --git a/extensions/BugmailFilter/lib/Filter.pm b/extensions/BugmailFilter/lib/Filter.pm index 9a0d0f89a..5e83bd52a 100644 --- a/extensions/BugmailFilter/lib/Filter.pm +++ b/extensions/BugmailFilter/lib/Filter.pm @@ -19,6 +19,7 @@ use Bugzilla::Extension::BugmailFilter::FakeField; use Bugzilla::Field; use Bugzilla::Product; use Bugzilla::User; +use Bugzilla::Util qw(trim); use constant DB_TABLE => 'bugmail_filters'; @@ -85,10 +86,20 @@ sub field_name { return $_[0]->{field_name} //= ''; } +sub field_description { + my ($self, $value) = @_; + $self->{field_description} = $value if defined($value); + return $self->{field_description}; +} + sub field { my ($self) = @_; return unless $self->{field_name}; if (!$self->{field}) { + if (substr($self->{field_name}, 0, 1) eq '~') { + # this should never happen + die "not implemented"; + } foreach my $field ( @{ Bugzilla::Extension::BugmailFilter::FakeField->fake_fields() }, @{ Bugzilla::Extension::BugmailFilter::FakeField->tracking_flag_fields() }, @@ -133,6 +144,14 @@ sub _check_user { sub _check_field_name { my ($class, $field_name) = @_; return undef unless $field_name; + if (substr($field_name, 0, 1) eq '~') { + $field_name = lc(trim($field_name)); + $field_name =~ /^~[a-z0-9_\.]+$/ + || ThrowUserError('bugmail_filter_invalid'); + length($field_name) <= 64 + || ThrowUserError('bugmail_filter_too_long'); + return $field_name; + } foreach my $rh (@{ FAKE_FIELD_NAMES() }) { return $field_name if $rh->{name} eq $field_name; } @@ -147,8 +166,16 @@ sub _check_field_name { sub matches { my ($self, $args) = @_; - if ($self->{field_name} && $self->{field_name} ne $args->{field_name}) { - return 0; + if (my $field_name = $self->{field_name}) { + if (substr($field_name, 0, 1) eq '~') { + my $substring = quotemeta(substr($field_name, 1)); + if ($args->{field}->{field_name} !~ /$substring/i) { + return 0; + } + } + elsif ($field_name ne $args->{field}->{filter_field}) { + return 0; + } } if ($self->{product_id} && $self->{product_id} != $args->{product_id}) { diff --git a/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl b/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl index 12c745adb..43d18f516 100644 --- a/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl +++ b/extensions/BugmailFilter/template/en/default/account/prefs/bugmail_filter.html.tmpl @@ -48,16 +48,24 @@ var cpts = new Array(); [% field.description FILTER html %] </option> [% END %] + <option value="~">Contains:</option> </select> </td> <td class="blurb"> the field that was changed </td> </tr> +<tr id="field_contains_row" class="bz_default_hidden"> + <td> </td> + <td> + <input name="field_contains" id="field_contains" + placeholder="field name" maxlength="63"> + </td> +</tr> <tr> <th>Product:</th> <td> - <select name="product" id="product" onChange="onFilterProductChange()"> + <select name="product" id="product"> <option value="">__Any__</option> [% FOREACH product IN selectable_products %] <option>[% product.name FILTER html %]</option> @@ -139,11 +147,11 @@ var cpts = new Array(); <tr class="[% "row_odd" UNLESS loop.count % 2 %]"> <td> <input type="checkbox" name="remove" value="[% filter.id FILTER none %]" - onChange="onRemoveChange()"> + onChange="onFilterRemoveChange()"> </td> <td>[% filter.product ? filter.product.name : 'Any' FILTER html %]</td> <td>[% filter.component ? filter.component.name : 'Any' FILTER html %]</td> - <td>[% filter.field ? fields.${filter.field.name} || filter.field.description : 'Any' FILTER html %]</td> + <td>[% filter.field_description FILTER html %]</td> <td>[% filter.relationship ? filter.relationship_name : 'Any' FILTER html %]</td> <td>[% filter.action ? 'Exclude' : 'Include' %]</td> </tr> diff --git a/extensions/BugmailFilter/template/en/default/hook/global/user-error-errors.html.tmpl b/extensions/BugmailFilter/template/en/default/hook/global/user-error-errors.html.tmpl index 380c07ee5..4be894c6f 100644 --- a/extensions/BugmailFilter/template/en/default/hook/global/user-error-errors.html.tmpl +++ b/extensions/BugmailFilter/template/en/default/hook/global/user-error-errors.html.tmpl @@ -10,4 +10,13 @@ [% title = "Filter Already Exists" %] A filter already exists with the selected criteria. +[% ELSIF error == "bugmail_filter_too_long" %] + [% title = "Invalid Field Name" %] + The field name to filter on is too long (must be 63 character or fewer). + +[% ELSIF error == "bugmail_filter_invalid" %] + [% title = "Invalid Field Name" %] + The field name contains invalid characters (alpha-numeric, underscore and + period only). + [% END %] diff --git a/extensions/BugmailFilter/web/js/bugmail-filter.js b/extensions/BugmailFilter/web/js/bugmail-filter.js index 2b320bbbb..c24528861 100644 --- a/extensions/BugmailFilter/web/js/bugmail-filter.js +++ b/extensions/BugmailFilter/web/js/bugmail-filter.js @@ -6,19 +6,33 @@ * defined by the Mozilla Public License, v. 2.0. */ var Dom = YAHOO.util.Dom; -var Event = YAHOO.util.Event; + +function onFilterFieldChange() { + if (Dom.get('field').value == '~') { + Dom.removeClass('field_contains_row', 'bz_default_hidden'); + Dom.get('field_contains').focus(); + Dom.get('field_contains').select(); + } + else { + Dom.addClass('field_contains_row', 'bz_default_hidden'); + } +} function onFilterProductChange() { selectProduct(Dom.get('product'), Dom.get('component'), null, null, '__Any__'); Dom.get('component').disabled = Dom.get('product').value == ''; } -function onFilterActionChange() { - var value = Dom.get('action').value; - Dom.get('add_filter').disabled = value == ''; +function setFilterAddEnabled() { + Dom.get('add_filter').disabled = + ( + Dom.get('field').value == '~' + && Dom.get('field_contains').value == '' + ) + || Dom.get('action').value == ''; } -function onRemoveChange() { +function onFilterRemoveChange() { var cbs = Dom.get('filters_table').getElementsByTagName('input'); for (var i = 0, l = cbs.length; i < l; i++) { if (cbs[i].checked) { @@ -34,9 +48,13 @@ function showAllFlags() { Dom.removeClass('all_flags', 'bz_default_hidden'); } -Event.onDOMReady(function() { - Event.on('action', 'change', onFilterActionChange); +YAHOO.util.Event.onDOMReady(function() { + YAHOO.util.Event.on('field', 'change', onFilterFieldChange); + YAHOO.util.Event.on('field_contains', 'keyup', setFilterAddEnabled); + YAHOO.util.Event.on('product', 'change', onFilterProductChange); + YAHOO.util.Event.on('action', 'change', setFilterAddEnabled); + onFilterFieldChange(); onFilterProductChange(); - onFilterActionChange(); - onRemoveChange(); + onFilterRemoveChange(); + setFilterAddEnabled(); }); diff --git a/extensions/TrackingFlags/Extension.pm b/extensions/TrackingFlags/Extension.pm index b107c0393..33cac7b9d 100644 --- a/extensions/TrackingFlags/Extension.pm +++ b/extensions/TrackingFlags/Extension.pm @@ -30,9 +30,14 @@ use JSON; our $VERSION = '1'; BEGIN { + *Bugzilla::tracking_flags = \&_tracking_flags; *Bugzilla::tracking_flag_names = \&_tracking_flag_names; } +sub _tracking_flags { + return Bugzilla::Extension::TrackingFlags::Flag->get_all(); +} + sub _tracking_flag_names { return Bugzilla::Extension::TrackingFlags::Flag->get_all_names(); } |