summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--extensions/ComponentWatching/Extension.pm116
-rw-r--r--extensions/ComponentWatching/template/en/default/account/prefs/component_watch.html.tmpl89
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>&nbsp;
- <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>&nbsp;
- <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>