Modified version of WWW::Bugzilla
[git-perl-utils.git] / lib / WWW / Bugzilla / Search.pm
diff --git a/lib/WWW/Bugzilla/Search.pm b/lib/WWW/Bugzilla/Search.pm
new file mode 100644 (file)
index 0000000..14fef9e
--- /dev/null
@@ -0,0 +1,313 @@
+package WWW::Bugzilla::Search;
+
+$WWW::Bugzilla::Search::VERSION = '0.2';
+
+use strict;
+use warnings;
+use WWW::Mechanize;
+use Carp qw(croak carp);
+use Params::Validate;
+use vars qw($VERSION @ISA @EXPORT);
+use WWW::Bugzilla;
+
+=head1 NAME
+
+WWW::Bugzilla::Search - Handles searching bugzilla bugs via WWW::Mechanize.
+
+=head1 SYNOPSIS
+
+    use WWW::Bugzilla::Search;
+
+    # Login
+    my $search = WWW::Bugzilla::Search->new(
+        server => 'bugs.example.com',
+        email => 'user@example.com',
+        password => 'my_passwd',
+    );
+
+    $search->summary('This is my summary');
+    my @bugs = $search->search();
+
+=head1 DESCRIPTION
+
+WWW::Bugzilla::Search provides an API to search for bugs in a Bugzilla database.  Any resulting bugs will be returned as instances of WWW::Bugzilla bugs.
+
+=head1 INTERFACE
+
+=head2 Multiple choice search criteria 
+
+The following fields are multiple choice fields: 
+
+classification, component, op_sys, priority, product, resolution, bug_severity, bug_status, target_milestone, version, hardware, rep_platform
+
+Available options can be retrieved via:
+    
+    $search->field();
+
+To choose a given value, use:
+    
+    $search->field('value');
+
+=head2 Text search criteria
+
+The following fields are avaiilable for text searching:
+
+assigned_to, reporter, summary
+
+To searc using a given field, use:
+
+    $search->field('value');
+
+=head2 Methods
+
+=head3 search()
+
+Searches Bugzilla with the defined criteria.  Returns a list of bugs that match the criteria.  Each bug is a seperate instance of WWW::Bugzilla
+
+=head3 reset()
+
+Resets all search criteria.
+
+=over
+
+=back
+
+=cut 
+
+
+#sub AUTOLOAD {
+#    my $self = shift;
+#    my $name = $AUTOLOAD;
+#
+#    warn Dumper($self->{'_fields'});
+#    if ($self->{'_fields'}{$name}) {
+#        $name =~ s/^WWW::Bugzilla::Search:://;
+#        my $out =$self->_field_values($name);
+#    #    warn Dumper($out);
+#    }
+#
+#    if (@_) {
+#        return $self->{$name} = shift;
+#    } else {
+#        if (defined($self->{$name})) {
+#            return $self->{$name};
+#        } else {
+#            return;
+#        }
+#    }
+#}
+
+my $_SETUP;
+
+sub new {
+    my $that  = shift;
+    my $class = ref($that) || $that;
+    my $self  = {
+        search_keys => {},
+        protocol => '',
+        server=> '',
+    };
+    bless $self, $class;
+
+    if (!$_SETUP) {
+        no strict 'refs';
+        # accessors
+        foreach my $field (qw(mech protocol server email password)) {
+            *{ $class . '::' . $field } = sub { my ($self, $value) = @_; if (defined $value) { $self->{$field} = $value; } return $self->{$field} }
+        }
+        
+        # search fields
+        foreach my $field (qw(classification component op_sys priority product resolution bug_severity bug_status target_milestone version hardware rep_platform)) {
+            *{ $class . '::' . $field } = sub {
+                my ($self, $value) = @_; 
+                if (defined $value) {
+                    $self->{'search_keys'}{$field} = $value;
+                    return $value;
+                } else {
+                    return $self->_field_values($field); 
+                }
+            }
+        }
+
+        # search fields that are used as accessors
+        foreach my $field (qw(assigned_to reporter summary)) {
+            *{ $class . '::' . $field } = sub { my ($self, $value) = @_; if (defined $value) { $self->{'search_keys'}{$field} = $value; } return $self->{'search_keys'}{$field} }
+        }
+        $_SETUP++;
+    }
+
+    if (@_) {
+        my %conf = @_;
+        while (my ($k, $v) = each %conf) {
+            $self->$k($v);
+        }
+    }
+
+    $self->protocol('http') if !$self->protocol();
+
+    $self->{'mech'} = WWW::Mechanize->new();
+    $self->_login();
+    
+    return $self;
+}
+
+sub _field_values {
+    my ($self, $name) = @_;
+    my $url = $self->protocol().'://'.$self->server().'/query.cgi?format=advanced';
+    my $mech = $self->{'mech'};
+    if ($mech->{'uri'} ne $url) {
+        $mech->get( $url); 
+    }
+    $mech->form_name('queryform');
+
+    my @values;
+
+    my $form = $mech->current_form();
+    foreach my $field ($form->inputs()) {
+        if ($field->name && $field->name eq $name) {
+            push (@values, grep { defined $_ }$field->possible_values());
+        }
+    }
+    if (@values) {
+        return grep { defined $_ } @values;
+    }
+    warn "no values for $name";
+    return;
+}
+
+sub reset {
+    my ($self) = @_;
+
+    $self->{'search_keys'} = {};
+}
+
+sub search {
+    my ($self) = @_;
+    my $mech = $self->{'mech'};
+    my $login_page = $self->protocol().'://'.$self->server().'/query.cgi?format=advanced';
+    $mech->get( $login_page ); 
+    $mech->form_name('queryform');
+
+    foreach my $key (keys %{ $self->{'search_keys'} }) {
+        my $value =  $self->{'search_keys'}{$key};
+        if ($key eq 'summary') {
+            $mech->field('short_desc', $value, 1);
+        } elsif ($key eq 'assigned_to') {
+            $mech->field('email1', $value);
+            $mech->field('emailtype1', 'regexp') if (ref($value) eq 'Regexp');
+        } elsif ($key eq 'reporter') {
+            $mech->field('email2', $value);
+            $mech->field('emailtype2', 'regexp') if (ref($value) eq 'Regexp');
+            $mech->tick('emailreporter2', 1);
+            map($mech->untick($_, 1), qw(emailqa_contact2 emailassigned_to2 emailcc2));
+        } else {
+            if ($self->_field_values($key)) {
+                # ghetto hack.  Grr, Mechanize is making each of the form elements a seperate entry.
+                my $i = 1;
+                foreach my $input ($mech->current_form()->inputs()) {
+                    next if (defined $input->name && $input->name ne $key);
+                    foreach my $val ($input->possible_values) {
+                        next if !defined $val;
+                        if ($value eq $val) {
+                            $input->value($value);
+                           # $mech->field($key, $value, $i);
+                        }
+                    }
+                    $i++;
+                }
+            } else {
+                $mech->field($key, $self->{'search_keys'}{$key});
+            }
+        }
+    }
+
+    $mech->submit();
+    my @bugs;
+    foreach my $link ($mech->links()) {
+        if ($link->url() =~ /^show_bug\.cgi\?id=(\d+)$/) {
+            push (@bugs, WWW::Bugzilla->new( 'server' => $self->{'server'}, 'email' => $self->{'email'}, 'password' => $self->{'password'}, 'bug_number' => $1, 'use_ssl' => ($self->protocol() eq 'https') ? 1 : 0));
+        }
+    }
+    return @bugs;
+}
+
+# based on the current page, set the current form to the first form with a specified field
+sub _get_form_by_field {
+    my ($self, $field) = @_;
+    croak("invalid field") if !$field;
+
+    my $mech = $self->{'mech'};
+    my $i = 1;
+    foreach my $form ($mech->forms()) {
+        if ($form->find_input($field)) {
+            $mech->form_number($i);
+            return 1;
+        }
+        $i++;
+    }
+    return;
+}
+
+sub _login {
+    my $self = shift;
+    my $mech = $self->{'mech'};
+    my $login_page = $self->protocol().'://'.$self->server().'/query.cgi?GoAheadAndLogIn=1';
+    $mech->get( $login_page ); 
+
+    # bail unless OK or Redirect happens
+    croak("Cannot open page $login_page") unless ( ($mech->status == '200') or ($mech->status == '404') );
+    croak("Login form is missing") if !$self->_get_form_by_field('Bugzilla_login');
+    $mech->field('Bugzilla_login', $self->email());
+    $mech->field('Bugzilla_password', $self->password());
+    $mech->submit_form();
+    $mech->get( $login_page );
+    croak("Login failed") if $self->_get_form_by_field('Bugzilla_login');
+}
+
+=head1 BUGS, IMPROVEMENTS
+
+There may well be bugs in this module.  Using it as I have, I just have not run
+into any.  In addition, this module does not support ALL of Bugzilla's
+features.  I will consider any patches or improvements, just send me an email
+at the address listed below.
+=head1 AUTHOR
+
+Written by:
+    Brian Caswell (bmc@shmoo.com)
+
+Portions taken from WWW::Bugzilla, originally written by:
+    Matthew C. Vella (the_mcv@yahoo.com)
+
+=head1 LICENSE
+                                                                      
+  WWW::Bugzilla::Search - Module providing API to search Bugzilla bugs.
+  Copyright (C) 2006 Brian Caswell (bmc@shmoo.com)
+
+  Portions Copyright (C) 2003 Matthew C. Vella (the_mcv@yahoo.com)
+                                                                    
+  This module is free software; you can redistribute it and/or modify it
+  under the terms of either:
+                                                                      
+  a) the GNU General Public License as published by the Free Software
+  Foundation; either version 1, or (at your option) any later version,                                                                      
+  or
+                                                                      
+  b) the "Artistic License" which comes with this module.
+                                                                      
+  This program is distributed in the hope that it will be useful,
+  but WITHOUT ANY WARRANTY; without even the implied warranty of
+  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See either
+  the GNU General Public License or the Artistic License for more details.
+                                                                      
+  You should have received a copy of the Artistic License with this
+  module, in the file ARTISTIC.  If not, I'll be glad to provide one.
+                                                                      
+  You should have received a copy of the GNU General Public License
+  along with this program; if not, write to the Free Software
+  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+  USA
+
+=cut
+
+1;