diff options
-rw-r--r-- | extensions/ComponentWatching/Extension.pm | 116 | ||||
-rw-r--r-- | extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl | 89 |
2 files changed, 133 insertions, 72 deletions
diff --git a/extensions/ComponentWatching/Extension.pm b/extensions/ComponentWatching/Extension.pm index f85e0010c..627fb12fd 100644 --- a/extensions/ComponentWatching/Extension.pm +++ b/extensions/ComponentWatching/Extension.pm @@ -14,7 +14,7 @@ use Bugzilla::Error; use Bugzilla::Group; use Bugzilla::User; use Bugzilla::User::Setting; -use Bugzilla::Util qw(trim); +use Bugzilla::Util qw(trim trick_taint); our $VERSION = '2'; @@ -210,35 +210,39 @@ sub user_preferences { my $input = Bugzilla->input_params; if ($save) { - my ($sth, $sthAdd, $sthDel); - if ($input->{'add'} && $input->{'add_product'}) { # add watch - my $productName = $input->{'add_product'}; - my $ra_componentNames = $input->{'add_component'}; - $ra_componentNames = [$ra_componentNames || ''] unless ref($ra_componentNames); - # load product and verify access + my $productName = $input->{'add_product'}; my $product = Bugzilla::Product->new({ name => $productName, cache => 1 }); unless ($product && $user->can_access_product($product)) { ThrowUserError('product_access_denied', { product => $productName }); } - if (grep { $_ eq '' } @$ra_componentNames) { - # watching a product - _addProductWatch($user, $product); + # starting-with + if (my $prefix = $input->{add_starting}) { + _addPrefixWatch($user, $product, $prefix); } else { - # watching specific components - foreach my $componentName (@$ra_componentNames) { - my $component = Bugzilla::Component->new({ - name => $componentName, product => $product, cache => 1 - }); - unless ($component) { - ThrowUserError('product_access_denied', { product => $productName }); + my $ra_componentNames = $input->{'add_component'}; + $ra_componentNames = [$ra_componentNames || ''] unless ref($ra_componentNames); + + if (grep { $_ eq '' } @$ra_componentNames) { + # watching a product + _addProductWatch($user, $product); + + } else { + # watching specific components + foreach my $componentName (@$ra_componentNames) { + my $component = Bugzilla::Component->new({ + name => $componentName, product => $product, cache => 1 + }); + unless ($component) { + ThrowUserError('product_access_denied', { product => $productName }); + } + _addComponentWatch($user, $component); } - _addComponentWatch($user, $component); } } @@ -247,14 +251,14 @@ sub user_preferences { } else { # remove watch(s) - foreach my $name (keys %$input) { - if ($name =~ /^del_(\d+)$/) { - _deleteProductWatch($user, $1); - } elsif ($name =~ /^del_(\d+)_(\d+)$/) { - _deleteComponentWatch($user, $1, $2); - } + my $delete = ref $input->{del_watch} + ? $input->{del_watch} + : [ $input->{del_watch} ]; + foreach my $id (@$delete) { + _deleteWatch($user, $id); } } + } $vars->{'add_product'} = $input->{'product'}; @@ -312,8 +316,19 @@ sub bugmail_recipients { FROM component_watch WHERE ((product_id = ? OR product_id = ?) AND component_id IS NULL) OR (component_id = ? OR component_id = ?) + UNION + SELECT user_id + FROM component_watch + INNER JOIN components ON components.product_id = component_watch.product_id + WHERE component_prefix IS NOT NULL + AND (component_watch.product_id = ? OR component_watch.product_id = ?) + AND components.name LIKE CONCAT(component_prefix, '%') "); - $sth->execute($oldProductId, $newProductId, $oldComponentId, $newComponentId); + $sth->execute( + $oldProductId, $newProductId, + $oldComponentId, $newComponentId, + $oldProductId, $newProductId + ); while (my ($uid) = $sth->fetchrow_array) { if (!exists $recipients->{$uid}) { $recipients->{$uid}->{+REL_COMPONENT_WATCHER} = Bugzilla::BugMail::BIT_WATCHING(); @@ -365,20 +380,22 @@ sub _getWatches { my $dbh = Bugzilla->dbh; my $sth = $dbh->prepare(" - SELECT product_id, component_id + SELECT id, product_id, component_id, component_prefix FROM component_watch WHERE user_id = ? "); $sth->execute($user->id); my @watches; - while (my ($productId, $componentId) = $sth->fetchrow_array) { + while (my ($id, $productId, $componentId, $prefix) = $sth->fetchrow_array) { my $product = Bugzilla::Product->new({ id => $productId, cache => 1 }); next unless $product && $user->can_access_product($product); my %watch = ( - product => $product, - product_name => $product->name, - component_name => '', + id => $id, + product => $product, + product_name => $product->name, + component_name => '', + component_prefix => $prefix, ); if ($componentId) { my $component = Bugzilla::Component->new({ id => $componentId, cache => 1 }); @@ -393,6 +410,7 @@ sub _getWatches { @watches = sort { $a->{'product_name'} cmp $b->{'product_name'} || $a->{'component_name'} cmp $b->{'component_name'} + || $a->{'component_prefix'} cmp $b->{'component_prefix'} } @watches; return \@watches; @@ -476,26 +494,40 @@ sub _addComponentWatch { $sth->execute($user->id, $component->product_id, $component->id); } -sub _deleteProductWatch { - my ($user, $productId) = @_; +sub _addPrefixWatch { + my ($user, $product, $prefix) = @_; my $dbh = Bugzilla->dbh; + trick_taint($prefix); my $sth = $dbh->prepare(" - DELETE FROM component_watch - WHERE user_id = ? AND product_id = ? AND component_id IS NULL + SELECT 1 + FROM component_watch + WHERE user_id = ? + AND ( + (product_id = ? AND component_prefix = ?) + OR (product_id = ? AND component_id IS NULL) + ) "); - $sth->execute($user->id, $productId); + $sth->execute( + $user->id, + $product->id, $prefix, + $product->id + ); + return if $sth->fetchrow_array; + + $sth = $dbh->prepare(" + INSERT INTO component_watch(user_id, product_id, component_prefix) + VALUES (?, ?, ?) + "); + $sth->execute($user->id, $product->id, $prefix); } -sub _deleteComponentWatch { - my ($user, $productId, $componentId) = @_; +sub _deleteWatch { + my ($user, $id) = @_; my $dbh = Bugzilla->dbh; - my $sth = $dbh->prepare(" - DELETE FROM component_watch - WHERE user_id = ? AND product_id = ? AND component_id = ? - "); - $sth->execute($user->id, $productId, $componentId); + trick_taint($id); + $dbh->do("DELETE FROM component_watch WHERE id=?", undef, $id); } sub _addDefaultSettings { diff --git a/extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl b/extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl index 8c193a056..5e27c1247 100644 --- a/extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl +++ b/extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl @@ -11,6 +11,18 @@ [% SET selectable_products = user.get_selectable_products %] [% SET dont_show_button = 1 %] +<style> +#add_compwatch input[type=text], +#add_compwatch select +{ + width: 30em; +} + +#component[disabled] { + color: silver; +} +</style> + <script> var Dom = YAHOO.util.Dom; var useclassification = false; @@ -70,6 +82,19 @@ function onSelectComponent() { Dom.get('add').disabled = Dom.get('component').selectedIndex == -1; } +function onStartingWith(el) { + var value = el.value.replace(/(^\s*|\s*$)/g, ''); + if (value == '') { + Dom.get('component').disabled = false; + onSelectProduct(); + } else { + Dom.get('component').selectedIndex = -1; + Dom.get('watch-user-div').style.display = 'none'; + Dom.get('component').disabled = true; + Dom.get('add').disabled = false; + } +} + YAHOO.util.Event.onDOMReady(onSelectProduct); function onRemoveChange() { @@ -88,15 +113,20 @@ YAHOO.util.Event.onDOMReady(onRemoveChange); </script> <p> - Select the components you want to watch. + Select the components you want to watch.<br> To watch all components in a product, watch "__Any__".<br> + Watching components starting with "Developer Tools" is the same as watching + "Developer Tools", "Developer Tools: Console", "Developer Tools: Debugger", + etc. If a new component is added which starts with "Developer Tools", you'll + automatically start watching that too. + <br> Use <a href="userprefs.cgi?tab=email">Email Preferences</a> to filter which notification emails you receive. </p> -<table border="0" cellpadding="3" cellspacing="0"> +<table border="0" cellpadding="3" cellspacing="0" id="add_compwatch"> <tr> - <td align="right">Product:</td> + <th align="right">Product:</th> <td colspan="2"> <select name="add_product" id="product" onChange="onSelectProduct()"> [% FOREACH product IN selectable_products %] @@ -107,9 +137,13 @@ YAHOO.util.Event.onDOMReady(onRemoveChange); </td> </tr> <tr> - <td align="right" valign="top">Component:</td> + <th align="right" valign="top">Component:</th> + <td>Select the component(s) to add to your watch list:</td> +</tr> +<tr> + <td></td> <td> - <select name="add_component" id="component" multiple size="10" onChange="onSelectComponent()"> + <select name="add_component" id="component" multiple size="5" onChange="onSelectComponent()"> <option value="">__Any__</option> [% FOREACH product IN selectable_products %] [% FOREACH component IN product.components %] @@ -117,7 +151,10 @@ YAHOO.util.Event.onDOMReady(onRemoveChange); [%~ component.name FILTER html %]</option> [% END %] [% END %] - </select> + </select><br> + Or watch components starting with:<br> + <input type="text" name="add_starting" id="add_starting" maxlength="64" + onKeyUp="onStartingWith(this)" onBlur="onStartingWith(this)"> </td> <td valign="top"> <div id="watch-user-div" @@ -149,38 +186,30 @@ YAHOO.util.Event.onDOMReady(onRemoveChange); </tr> [% FOREACH watch IN watches %] <tr> - [% IF (watch.component) %] <td> - <input type="checkbox" onChange="onRemoveChange()" id="cwdel_[% loop.count %]" value="1" - name="del_[% watch.product.id FILTER html %]_[% watch.component.id FILTER html %]"> + <input type="checkbox" onChange="onRemoveChange()" + id="cwdel_[% watch.id FILTER none %]" + name="del_watch" value="[% watch.id FILTER none %]"> </td> <td> - <label for="cwdel_[% loop.count %]"> - [% watch.component.product.name FILTER html %] - </label> - </td> - <td> - <a href="buglist.cgi?product=[% watch.product.name FILTER uri ~%] - &component=[% watch.component.name FILTER uri %]&resolution=---"> - [% watch.component.name FILTER html %] - </a> - </td> - [% ELSE %] - <td> - <input type="checkbox" onChange="onRemoveChange()" id="cwdel_[% loop.count %]" value="1" - name="del_[% watch.product.id FILTER html %]" value="1"> - </td> - <td> - <label for="cwdel_[% loop.count %]"> + <label for="cwdel_[% watch.id FILTER none %]"> [% watch.product.name FILTER html %] </label> </td> <td> - <a href="describecomponents.cgi?product=[% watch.product.name FILTER uri %]"> - __Any__ - </a> + [% IF (watch.component) %] + <a href="buglist.cgi?product=[% watch.product.name FILTER uri ~%] + &component=[% watch.component.name FILTER uri %]&resolution=---"> + [% watch.component.name FILTER html %] + </a> + [% ELSIF watch.component_prefix %] + <i>starts with:</i> [% watch.component_prefix FILTER html %] + [% ELSE %] + <a href="describecomponents.cgi?product=[% watch.product.name FILTER uri %]"> + __Any__ + </a> + [% END %] </td> - [% END %] </tr> [% END %] </table> |