summaryrefslogtreecommitdiffstats
path: root/extensions/ProdCompSearch/lib/WebService.pm
blob: 3f8f0b75eb108a4436934aea2952f602b6c0f9f2 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
#
# This Source Code Form is "Incompatible With Secondary Licenses", as
# defined by the Mozilla Public License, v. 2.0.

package Bugzilla::Extension::ProdCompSearch::WebService;

use strict;
use warnings;

use base qw(Bugzilla::WebService);

use Bugzilla::Error;
use Bugzilla::Util qw(detaint_natural trick_taint trim);

sub prod_comp_search {
    my ($self, $params) = @_;
    my $user = Bugzilla->user;
    my $dbh = Bugzilla->switch_to_shadow_db();

    my $search = trim($params->{'search'} || '');
    $search || ThrowCodeError('param_required',
        { function => 'PCS.prod_comp_search', param => 'search' });

    my $limit = detaint_natural($params->{'limit'})
                ? $dbh->sql_limit($params->{'limit'})
                : '';

    # We do this in the DB directly as we want it to be fast and
    # not have the overhead of loading full product objects

    # All products which the user has "Entry" access to.
    my $enterable_ids = $dbh->selectcol_arrayref(
           'SELECT products.id FROM products
         LEFT JOIN group_control_map
                   ON group_control_map.product_id = products.id
                      AND group_control_map.entry != 0
                      AND group_id NOT IN (' . $user->groups_as_string . ')
            WHERE group_id IS NULL
                  AND products.isactive = 1');

    if (scalar @$enterable_ids) {
        # And all of these products must have at least one component
        # and one version.
        $enterable_ids = $dbh->selectcol_arrayref(
            'SELECT DISTINCT products.id FROM products
              WHERE ' . $dbh->sql_in('products.id', $enterable_ids) .
              ' AND products.id IN (SELECT DISTINCT components.product_id
                                      FROM components
                                     WHERE components.isactive = 1)
                AND products.id IN (SELECT DISTINCT versions.product_id
                                      FROM versions
                                     WHERE versions.isactive = 1)');
    }

    return { products => [] } if !scalar @$enterable_ids;

    trick_taint($search);
    my @terms;
    my @order;

    if ($search =~ /^(.*?)::(.*)$/) {
        my ($product, $component) = (trim($1), trim($2));
        push @terms, _build_terms($product, 1, 0);
        push @terms, _build_terms($component, 0, 1);
        push @order, "products.name != " . $dbh->quote($product) if $product ne '';
        push @order, "components.name != " . $dbh->quote($component) if $component ne '';
        push @order, "products.name";
        push @order, "components.name";
    } else {
        push @terms, _build_terms($search, 1, 1);
        push @order, "products.name != " . $dbh->quote($search);
        push @order, "components.name != " . $dbh->quote($search);
        push @order, "products.name";
        push @order, "components.name";
    }
    return { products => [] } if !scalar @terms;

    # To help mozilla staff file bmo administration bugs into the right
    # component, sort bmo first when searching for 'bugzilla'
    if ($search =~ /bugzilla/i && $search !~ /^bugzilla\s*::/i
        && ($user->in_group('mozilla-corporation') || $user->in_group('mozilla-foundation')))
    {
        unshift @order, "products.name != 'bugzilla.mozilla.org'";
    }

    my $products = $dbh->selectall_arrayref("
        SELECT products.name AS product,
               components.name AS component
          FROM products
               INNER JOIN components ON products.id = components.product_id
         WHERE (" . join(" AND ", @terms) . ")
               AND products.id IN (" . join(",", @$enterable_ids) . ")
      ORDER BY " . join(", ", @order) . " $limit",
        { Slice => {} });

    return { products => $products };
}

sub _build_terms {
    my ($query, $product, $component) = @_;
    my $dbh = Bugzilla->dbh();

    my @fields;
    push @fields, 'products.name', 'products.description' if $product;
    push @fields, 'components.name', 'components.description' if $component;
    # note: CONCAT_WS is MySQL specific
    my $field = "CONCAT_WS(' ', ". join(',', @fields) . ")";

    my @terms;
    foreach my $word (split(/[\s,]+/, $query)) {
        push(@terms, $dbh->sql_iposition($dbh->quote($word), $field) . " > 0")
            if $word ne '';
    }
    return @terms;
}

1;