Bug 162990 Shorthand/wildcard entry for login names in assign, cc, qa, fields
patch by not_erik@dasbistro.com r=joel, myk git-svn-id: svn://10.0.0.236/trunk@132500 18797224-902f-48f8-a5cc-f745e15eee43
This commit is contained in:
@@ -18,6 +18,7 @@
|
||||
# Rights Reserved.
|
||||
#
|
||||
# Contributor(s): Myk Melez <myk@mozilla.org>
|
||||
# Erik Stambaugh <not_erik@dasbistro.com>
|
||||
|
||||
################################################################################
|
||||
# Module Initialization
|
||||
@@ -79,33 +80,255 @@ sub new {
|
||||
|
||||
sub match {
|
||||
# Generates a list of users whose login name (email address) or real name
|
||||
# matches a substring.
|
||||
# matches a substring or wildcard.
|
||||
|
||||
# $str contains the string to match against, while $limit contains the
|
||||
# $str contains the string to match, while $limit contains the
|
||||
# maximum number of records to retrieve.
|
||||
my ($str, $limit, $exclude_disabled) = @_;
|
||||
|
||||
# Build the query.
|
||||
my $sqlstr = &::SqlQuote($str);
|
||||
my $qry = "
|
||||
SELECT userid, realname, login_name
|
||||
FROM profiles
|
||||
WHERE (INSTR(login_name, $sqlstr) OR INSTR(realname, $sqlstr))
|
||||
";
|
||||
$qry .= "AND disabledtext = '' " if $exclude_disabled;
|
||||
$qry .= "ORDER BY realname, login_name ";
|
||||
$qry .= "LIMIT $limit " if $limit;
|
||||
my @users = ();
|
||||
|
||||
# Execute the query, retrieve the results, and make them into User objects.
|
||||
my @users;
|
||||
&::PushGlobalSQLState();
|
||||
&::SendSQL($qry);
|
||||
push(@users, new Bugzilla::User(&::FetchSQLData())) while &::MoreSQLData();
|
||||
&::PopGlobalSQLState();
|
||||
return \@users if $str =~ /^\s*$/;
|
||||
|
||||
# The search order is wildcards, then exact match, then INSTR search.
|
||||
# Wildcard matching is skipped if there is no '*', and exact matches will
|
||||
# not (?) have a '*' in them. If any search comes up with something, the
|
||||
# ones following it will not execute.
|
||||
|
||||
# first try wildcards
|
||||
|
||||
my $wildstr = $str;
|
||||
|
||||
if ($wildstr =~ s/\*/\%/g) { # don't do wildcards if no '*' in the string
|
||||
|
||||
# Build the query.
|
||||
my $sqlstr = &::SqlQuote($wildstr);
|
||||
my $query = "SELECT userid, realname, login_name " .
|
||||
"FROM profiles " .
|
||||
"WHERE (login_name LIKE $sqlstr " .
|
||||
"OR realname LIKE $sqlstr) ";
|
||||
$query .= "AND disabledtext = '' " if $exclude_disabled;
|
||||
$query .= "ORDER BY length(login_name) ";
|
||||
$query .= "LIMIT $limit " if $limit;
|
||||
|
||||
# Execute the query, retrieve the results, and make them into
|
||||
# User objects.
|
||||
|
||||
&::PushGlobalSQLState();
|
||||
&::SendSQL($query);
|
||||
push(@users, new Bugzilla::User(&::FetchSQLData())) while &::MoreSQLData();
|
||||
&::PopGlobalSQLState();
|
||||
|
||||
}
|
||||
else { # try an exact match
|
||||
|
||||
my $sqlstr = &::SqlQuote($str);
|
||||
my $query = "SELECT userid, realname, login_name " .
|
||||
"FROM profiles " .
|
||||
"WHERE login_name = $sqlstr ";
|
||||
$query .= "AND disabledtext = '' " if $exclude_disabled;
|
||||
|
||||
&::PushGlobalSQLState();
|
||||
&::SendSQL($query);
|
||||
push(@users, new Bugzilla::User(&::FetchSQLData())) if &::MoreSQLData();
|
||||
&::PopGlobalSQLState();
|
||||
}
|
||||
|
||||
# then try instr
|
||||
|
||||
if ((scalar(@users) == 0)
|
||||
&& (&::Param('usermatchmode') eq 'search')
|
||||
&& (length($str) >= 3))
|
||||
{
|
||||
|
||||
my $sqlstr = &::SqlQuote($str);
|
||||
|
||||
my $query = "SELECT userid, realname, login_name " .
|
||||
"FROM profiles " .
|
||||
"WHERE (INSTR(login_name, $sqlstr) " .
|
||||
"OR INSTR(realname, $sqlstr)) ";
|
||||
$query .= "AND disabledtext = '' " if $exclude_disabled;
|
||||
$query .= "ORDER BY length(login_name) ";
|
||||
$query .= "LIMIT $limit " if $limit;
|
||||
|
||||
&::PushGlobalSQLState();
|
||||
&::SendSQL($query);
|
||||
push(@users, new Bugzilla::User(&::FetchSQLData())) while &::MoreSQLData();
|
||||
&::PopGlobalSQLState();
|
||||
}
|
||||
|
||||
# order @users by alpha
|
||||
|
||||
@users = sort { uc($a->{'email'}) cmp uc($b->{'email'}) } @users;
|
||||
|
||||
return \@users;
|
||||
}
|
||||
|
||||
# match_field() is a CGI wrapper for the match() function.
|
||||
#
|
||||
# Here's what it does:
|
||||
#
|
||||
# 1. Accepts a list of fields along with whether they may take multiple values
|
||||
# 2. Takes the values of those fields from $::FORM and passes them to match()
|
||||
# 3. Checks the results of the match and displays confirmation or failure
|
||||
# messages as appropriate.
|
||||
#
|
||||
# The confirmation screen functions the same way as verify-new-product and
|
||||
# confirm-duplicate, by rolling all of the state information into a
|
||||
# form which is passed back, but in this case the searched fields are
|
||||
# replaced with the search results.
|
||||
#
|
||||
# The act of displaying the confirmation or failure messages means it must
|
||||
# throw a template and terminate. When confirmation is sent, all of the
|
||||
# searchable fields have been replaced by exact fields and the calling script
|
||||
# is executed as normal.
|
||||
#
|
||||
# match_field must be called early in a script, before anything external is
|
||||
# done with the form data.
|
||||
#
|
||||
# In order to do a simple match without dealing with templates, confirmation,
|
||||
# or globals, simply calling Bugzilla::User::match instead will be
|
||||
# sufficient.
|
||||
|
||||
# How to call it:
|
||||
#
|
||||
# Bugzilla::User::match_field ({
|
||||
# 'field_name' => { 'type' => fieldtype },
|
||||
# 'field_name2' => { 'type' => fieldtype },
|
||||
# [...]
|
||||
# });
|
||||
#
|
||||
# fieldtype can be either 'single' or 'multi'.
|
||||
#
|
||||
|
||||
sub match_field {
|
||||
|
||||
my $fields = shift; # arguments as a hash
|
||||
my $matches = {}; # the values sent to the template
|
||||
my $matchsuccess = 1; # did the match fail?
|
||||
my $need_confirm = 0; # whether to display confirmation screen
|
||||
|
||||
# prepare default form values
|
||||
|
||||
my $vars = $::vars;
|
||||
$vars->{'form'} = \%::FORM;
|
||||
$vars->{'mform'} = \%::MFORM;
|
||||
|
||||
# Skip all of this if the option has been turned off
|
||||
return 1 if (&::Param('usermatchmode') eq 'off');
|
||||
|
||||
for my $field (keys %{$fields}) {
|
||||
|
||||
# Tolerate fields that do not exist.
|
||||
#
|
||||
# This is so that fields like qa_contact can be specified in the code
|
||||
# and it won't break if $::MFORM does not define them.
|
||||
#
|
||||
# It has the side-effect that if a bad field name is passed it will be
|
||||
# quietly ignored rather than raising a code error.
|
||||
|
||||
next if !defined($vars->{'mform'}->{$field});
|
||||
|
||||
# We need to move the query to $raw_field, where it will be split up,
|
||||
# modified by the search, and put back into $::FORM and $::MFORM
|
||||
# incrementally.
|
||||
|
||||
my $raw_field = join(" ", @{$vars->{'mform'}->{$field}});
|
||||
$vars->{'form'}->{$field} = '';
|
||||
$vars->{'mform'}->{$field} = [];
|
||||
|
||||
my @queries = ();
|
||||
|
||||
# Now we either split $raw_field by spaces/commas and put the list
|
||||
# into @queries, or in the case of fields which only accept single
|
||||
# entries, we simply use the verbatim text.
|
||||
|
||||
$raw_field =~ s/^\s+|\s+$//sg; # trim leading/trailing space
|
||||
|
||||
# single field
|
||||
if ($fields->{$field}->{'type'} eq 'single') {
|
||||
@queries = ($raw_field) unless $raw_field =~ /^\s*$/;
|
||||
|
||||
# multi-field
|
||||
}
|
||||
elsif ($fields->{$field}->{'type'} eq 'multi') {
|
||||
@queries = split(/[\s,]+/, $raw_field);
|
||||
|
||||
}
|
||||
else {
|
||||
# bad argument
|
||||
$vars->{'argument'} = $fields->{$field}->{'type'};
|
||||
$vars->{'function'} = 'Bugzilla::User::match_field';
|
||||
&::ThrowCodeError('bad_arg');
|
||||
}
|
||||
|
||||
for my $query (@queries) {
|
||||
|
||||
my $users = match(
|
||||
$query, # match string
|
||||
(&::Param('maxusermatches') || 0) + 1, # match limit
|
||||
1 # exclude_disabled
|
||||
);
|
||||
|
||||
# skip confirmation for exact matches
|
||||
if ((scalar(@{$users}) == 1)
|
||||
&& (@{$users}[0]->{'email'} eq $query))
|
||||
{
|
||||
$vars->{'form'}->{$field} .= @{$users}[0]->{'email'} . " ";
|
||||
push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'email'} . " ";
|
||||
next;
|
||||
}
|
||||
|
||||
$matches->{$field}->{$query}->{'users'} = $users;
|
||||
$matches->{$field}->{$query}->{'status'} = 'success';
|
||||
$matches->{$field}->{$query}->{'selecttype'} =
|
||||
$fields->{$field}->{'type'};
|
||||
|
||||
# here is where it checks for multiple matches
|
||||
|
||||
if (scalar(@{$users}) == 1) {
|
||||
# exactly one match
|
||||
$vars->{'form'}->{$field} .= @{$users}[0]->{'email'} . " ";
|
||||
push @{$vars->{'mform'}->{$field}}, @{$users}[0]->{'email'} . " ";
|
||||
$need_confirm = 1 if &::Param('confirmuniqueusermatch');
|
||||
|
||||
}
|
||||
elsif ((scalar(@{$users}) > 1)
|
||||
&& (&::Param('maxusermatches') != 1)) {
|
||||
$need_confirm = 1;
|
||||
|
||||
if ((&::Param('maxusermatches'))
|
||||
&& (scalar(@{$users}) > &::Param('maxusermatches')))
|
||||
{
|
||||
$matches->{$field}->{$query}->{'status'} = 'trunc';
|
||||
pop @{$users}; # take the last one out
|
||||
}
|
||||
|
||||
}
|
||||
else {
|
||||
# everything else fails
|
||||
$matchsuccess = 0; # fail
|
||||
$matches->{$field}->{$query}->{'status'} = 'fail';
|
||||
$need_confirm = 1; # confirmation screen shows failures
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return 1 unless $need_confirm; # skip confirmation if not needed.
|
||||
|
||||
$vars->{'script'} = $ENV{'SCRIPT_NAME'}; # for self-referencing URLs
|
||||
$vars->{'matches'} = $matches; # matches that were made
|
||||
$vars->{'matchsuccess'} = $matchsuccess; # continue or fail
|
||||
|
||||
print "Content-type: text/html\n\n";
|
||||
|
||||
$::template->process("global/confirm-user-match.html.tmpl", $vars)
|
||||
|| &::ThrowTemplateError($::template->error());
|
||||
|
||||
exit;
|
||||
|
||||
}
|
||||
|
||||
sub email_prefs {
|
||||
# Get or set (not implemented) the user's email notification preferences.
|
||||
|
||||
|
||||
@@ -879,6 +879,39 @@ Reason: %reason%
|
||||
default => '32',
|
||||
checker => \&check_netmask
|
||||
},
|
||||
|
||||
{
|
||||
name => 'usermatchmode',
|
||||
desc => 'Allow match strings to be entered for user names when entering ' .
|
||||
'and editing bugs. <p>' .
|
||||
'"off" disables matching,<br> ' .
|
||||
'"wildcard" allows only wildcards,<br> ' .
|
||||
'and "search" allows both wildcards and substring (freetext) ' .
|
||||
'matches.',
|
||||
type => 's',
|
||||
choices => ['off', 'wildcard', 'search'],
|
||||
default => 'off'
|
||||
},
|
||||
|
||||
{
|
||||
name => 'maxusermatches',
|
||||
desc => 'Search for no more than this many matches. <br>'.
|
||||
'If set to "1", no users will be displayed on ambiguous matches. '.
|
||||
'This is useful for user privacy purposes. <br>'.
|
||||
'A value of zero means no limit.',
|
||||
type => 't',
|
||||
default => '1000',
|
||||
checker => \&check_numeric
|
||||
},
|
||||
|
||||
{
|
||||
name => 'confirmuniqueusermatch',
|
||||
desc => 'Whether a confirmation screen should be displayed when only ' .
|
||||
'one user matches a search entry',
|
||||
type => 'b',
|
||||
default => 1,
|
||||
},
|
||||
|
||||
);
|
||||
|
||||
1;
|
||||
|
||||
@@ -29,6 +29,8 @@ use lib qw(.);
|
||||
require "CGI.pl";
|
||||
require "bug_form.pl";
|
||||
|
||||
use Bugzilla::User;
|
||||
|
||||
# Shut up misguided -w warnings about "used only once". For some reason,
|
||||
# "use vars" chokes on me when I try it here.
|
||||
sub sillyness {
|
||||
@@ -51,6 +53,12 @@ use vars qw($vars $template);
|
||||
ConnectToDatabase();
|
||||
my $whoid = confirm_login();
|
||||
|
||||
# do a match on the fields if applicable
|
||||
|
||||
&Bugzilla::User::match_field ({
|
||||
'cc' => { 'type' => 'multi' },
|
||||
'assigned_to' => { 'type' => 'single' },
|
||||
});
|
||||
|
||||
# The format of the initial comment can be structured by adding fields to the
|
||||
# enter_bug template and then referencing them in the comment template.
|
||||
|
||||
@@ -34,6 +34,8 @@ use lib qw(.);
|
||||
require "CGI.pl";
|
||||
require "bug_form.pl";
|
||||
|
||||
use Bugzilla::User;
|
||||
|
||||
use RelationSet;
|
||||
|
||||
# Use the Flag module to modify flag data if the user set flags.
|
||||
@@ -86,6 +88,14 @@ if (defined $::FORM{'id'}) {
|
||||
# Make sure there are bugs to process.
|
||||
scalar(@idlist) || ThrowUserError("no_bugs_chosen");
|
||||
|
||||
# do a match on the fields if applicable
|
||||
|
||||
&Bugzilla::User::match_field({
|
||||
'qa_contact' => { 'type' => 'single' },
|
||||
'newcc' => { 'type' => 'multi' },
|
||||
'assigned_to' => { 'type' => 'single' },
|
||||
});
|
||||
|
||||
# If we are duping bugs, let's also make sure that we can change
|
||||
# the original. This takes care of issue A on bug 96085.
|
||||
if (defined $::FORM{'dup_id'} && $::FORM{'knob'} eq "duplicate") {
|
||||
|
||||
@@ -90,6 +90,10 @@
|
||||
Attempted to add bug to an inactive group, identified by the bit
|
||||
'[% bit FILTER html %]'.
|
||||
|
||||
[% ELSIF error == "bad_arg" %]
|
||||
Bad argument <code>[% argument %]</code> sent to
|
||||
<code>[% function %]</code> function.
|
||||
|
||||
[% ELSIF error == "invalid_attach_id_to_obsolete" %]
|
||||
The attachment number of one of the attachments you wanted to obsolete,
|
||||
[% attach_id FILTER html %], is invalid.
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
"bug_id" => "Bug ID",
|
||||
"bug_severity" => "Severity",
|
||||
"bug_status" => "Status",
|
||||
"cc" => "CC",
|
||||
"cclist_accessible" => "CC list accessible?",
|
||||
"component_id" => "Component ID",
|
||||
"component" => "Component",
|
||||
@@ -37,6 +38,7 @@
|
||||
"everconfirmed" => "Ever confirmed?",
|
||||
"groupset" => "Groupset",
|
||||
"keywords" => "Keywords",
|
||||
"newcc" => "CC",
|
||||
"op_sys" => "OS",
|
||||
"percentage_complete" => "%Complete",
|
||||
"priority" => "Priority",
|
||||
|
||||
Reference in New Issue
Block a user