[Rt-commit] rt branch, 4.4/role-group-input, created. rt-4.4.1-126-g44393ea

Shawn Moore shawn at bestpractical.com
Thu May 18 13:23:23 EDT 2017


The branch, 4.4/role-group-input has been created
        at  44393ea4a186537b2b349b76fa99a6dffa16d1ff (commit)

- Log -----------------------------------------------------------------
commit 74207618401699c991daa91adb88682975a14a0a
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Thu May 18 15:34:25 2017 +0000

    Reuse CanonicalizePrincipal in DeleteRoleMember
    
    This matches the behavior of AddRoleMember. It's important for followup
    commits to be able to support, say, remove "Group:Foo" as AdminCc in
    bulk update.
    
    This also fixes a bug where the internal "RT::CustomRole-#" syntax may
    show up in error messages when removing a watcher from a custom role
    fails.

diff --git a/lib/RT/Record/Role/Roles.pm b/lib/RT/Record/Role/Roles.pm
index 284c6c0..2439c4c 100644
--- a/lib/RT/Record/Role/Roles.pm
+++ b/lib/RT/Record/Role/Roles.pm
@@ -507,14 +507,9 @@ Takes a set of key-value pairs:
 
 =over 4
 
-=item PrincipalId
-
-Optional.  The ID of the L<RT::Principal> object to remove.
-
-=item User
+=item Principal, PrincipalId, User, or Group
 
-Optional.  The Name or EmailAddress of an L<RT::User> to use as the
-principal
+Required. Canonicalized through L</CanonicalizePrincipal>.
 
 =item Type
 
@@ -528,8 +523,6 @@ status of "Permission denied".
 
 =back
 
-One, and only one, of I<PrincipalId> or I<User> is required.
-
 Returns a tuple of (principal object that was removed, message).
 
 =cut
@@ -541,20 +534,8 @@ sub DeleteRoleMember {
     return (0, $self->loc("That role is invalid for this object"))
         unless $args{Type} and $self->HasRole($args{Type});
 
-    if ($args{User}) {
-        my $user = RT::User->new( $self->CurrentUser );
-        $user->LoadByEmail( $args{User} );
-        $user->Load( $args{User} ) unless $user->id;
-        return (0, $self->loc("Could not load user '[_1]'", $args{User}) )
-            unless $user->id;
-        $args{PrincipalId} = $user->PrincipalId;
-    }
-
-    return (0, $self->loc("No valid PrincipalId"))
-        unless $args{PrincipalId};
-
-    my $principal = RT::Principal->new( $self->CurrentUser );
-    $principal->Load( $args{PrincipalId} );
+    my ($principal, $msg) = $self->CanonicalizePrincipal(%args);
+    return (0, $msg) if !$principal;
 
     my $acl = delete $args{ACL};
     return (0, $self->loc("Permission denied"))
@@ -568,12 +549,12 @@ sub DeleteRoleMember {
                             $principal->Object->Name, $self->loc($args{Type}) ) )
         unless $group->HasMember($principal);
 
-    my ($ok, $msg) = $group->_DeleteMember($args{PrincipalId}, RecordTransaction => !$args{Silent});
+    ((my $ok), $msg) = $group->_DeleteMember($principal->Id, RecordTransaction => !$args{Silent});
     unless ($ok) {
-        $RT::Logger->error("Failed to remove $args{PrincipalId} as a member of group ".$group->Id.": ".$msg);
+        $RT::Logger->error("Failed to remove ".$principal->Id." as a member of group ".$group->Id.": ".$msg);
 
         return ( 0, $self->loc('Could not remove [_1] as a [_2]',
-                    $principal->Object->Name, $self->loc($args{Type})) );
+                    $principal->Object->Name, $group->Label) );
     }
 
     return ($principal, $msg);

commit a9b067afad596824f3241664301516908e47d037
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 17 22:47:44 2017 +0000

    Handle Group:Name and Group:ID in role input fields
    
    This was already implicitly available by providing a numeric id in the
    role input field, but this capability makes it easier for end users.
    
    This reuses _ResolveRoles in /Ticket/Create.html to scan for RT
    addresses; without this change, anything other than email addresses
    (specifically Group:Foo) will be stripped from the builtin role input
    fields. (This check must be done on /Ticket/Create.html, not later, so
    that we can cancel the form submission and let the user fix their
    error.)

diff --git a/lib/RT/Record/Role/Roles.pm b/lib/RT/Record/Role/Roles.pm
index 2439c4c..47b6425 100644
--- a/lib/RT/Record/Role/Roles.pm
+++ b/lib/RT/Record/Role/Roles.pm
@@ -389,8 +389,14 @@ sub CanonicalizePrincipal {
                 if RT::EmailParser->IsRTAddress( $email );
         }
     } else {
-        if ($args{User}) {
+        if (($args{User}||'') =~ /^\s*Group\s*:\s*(.+?)\s*$/) {
+            $args{Group} = $1;
+            delete $args{User};
+        }
+        elsif ($args{User}) {
             my $name = delete $args{User};
+            delete $args{Group};
+
             # Sanity check the address
             return (0, $self->loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop",
                                   $name, $self->loc($args{Type}) ))
@@ -411,7 +417,8 @@ sub CanonicalizePrincipal {
             }
             $args{PrincipalId} = $user->PrincipalId;
         }
-        elsif ($args{Group}) {
+
+        if ($args{Group}) {
             my $name = delete $args{Group};
             my $group = RT::Group->new( $self->CurrentUser );
             $group->LoadUserDefinedGroup($name);
@@ -590,7 +597,18 @@ sub _ResolveRoles {
         } else {
             $roles->{$role} = [];
             my @values = ref $args{ $role } ? @{ $args{$role} } : ($args{$role});
-            for my $value (grep {defined} @values) {
+
+            while (my $value = shift @values) {
+                # extract "Group:xyz" early because Email::Address::List
+                # chokes on trying to parse that as an email address.
+                # (but not if "Group:xyz" is the very first entry, otherwise
+                # you'll get stuck in an infinite loop)
+                while ($value =~ s/,\s*(Group\s*:\s*[^,]+)($|,)/$2/) {
+                    push @values, $1;
+                }
+
+                next unless ($value||'') =~ /\S/;
+
                 if ( $value =~ /^\d+$/ ) {
                     # This implicitly allows groups, if passed by id.
                     my $principal = RT::Principal->new( $self->CurrentUser );
@@ -601,6 +619,18 @@ sub _ResolveRoles {
                         push @errors,
                             $self->loc("Couldn't load principal: [_1]", $msg);
                     }
+                } elsif ($value =~ /^\s*Group\s*:\s*([^,]+),?(.*)/) {
+                    my ($name, $rest) = ($1, $2);
+                    push @values, $rest;
+
+                    my $group = RT::Group->new( $self->CurrentUser );
+                    my ($ok, $msg) = $group->LoadUserDefinedGroup( $name );
+                    if ($ok) {
+                        push @{ $roles->{$role} }, $group->PrincipalObj;
+                    } else {
+                        push @errors,
+                            $self->loc("Couldn't load group [_1]: [_1]", $name, $msg);
+                    }
                 } else {
                     my @addresses = RT::EmailParser->ParseEmailAddress( $value );
                     for my $address ( @addresses ) {
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index e396857..f8546da 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -463,17 +463,18 @@ if ( !exists $ARGS{'AddMoreAttach'} && ($ARGS{'id'}||'') eq 'new' ) {
 
 # check email addresses for RT's
 {
+    my $roles = {};
+    $QueueObj->_ResolveRoles( $roles, %ARGS );
+
     foreach my $field ( qw(Requestors Cc AdminCc) ) {
-        my $value = $ARGS{ $field };
-        next unless defined $value && length $value;
+        for my $principal (@{ $roles->{ $field } }) {
+            next unless $principal->IsUser;
+            my $email = $principal->Object->EmailAddress;
+            next unless RT::EmailParser->IsRTAddress($email);
 
-        my @emails = Email::Address->parse( $value );
-        foreach my $email ( grep RT::EmailParser->IsRTAddress($_->address), @emails ) {
-            push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email->format, loc($field =~ /^(.*?)s?$/) );
+            push @results, loc("[_1] is an address RT receives mail at. Adding it as a '[_2]' would create a mail loop", $email, loc($field =~ /^(.*?)s?$/) );
             $checks_failure = 1;
-            $email = undef;
         }
-        $ARGS{ $field } = join ', ', map $_->format, grep defined, @emails;
     }
 }
 

commit ab2a0196cd42350ef4f37cffc4cc071463c7e989
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 17 21:31:27 2017 +0000

    Autocompleter for Principals (users and groups)
    
    This is a combination of the autocompleter for users and groups. Both
    object types will be searched, and their results will be displayed
    alongside eachother. The return value is hardcoded: for users, return
    their email address, for groups, return "Group:Name" (or if "Name"
    matches any autocomplete delimiter, "Group:ID"). This corresponds with
    what RT::Record::Role::Roles::_ResolveRoles accepts.
    
    Autocompleters can now display an extra line of information (called
    "detail"); for Principals it's used to display whether the record is a
    user or group.

diff --git a/share/html/Elements/EmailInput b/share/html/Elements/EmailInput
index 9e078d7..f327018 100644
--- a/share/html/Elements/EmailInput
+++ b/share/html/Elements/EmailInput
@@ -60,7 +60,7 @@
 % }
 
 % if ($Autocomplete) {
-    data-autocomplete="Users"
+    data-autocomplete="<% $AutocompleteType %>"
 % }
 
 % if ($AutocompleteMultiple) {
@@ -91,6 +91,7 @@ $Name
 $Size    => 40
 $Default => ''
 $Autocomplete => 1
+$AutocompleteType => 'Users'
 $AutocompleteMultiple => 0
 $AutocompleteReturn => ''
 $AutocompleteNobody => 0
diff --git a/share/html/Helpers/Autocomplete/Principals b/share/html/Helpers/Autocomplete/Principals
new file mode 100644
index 0000000..80b8fd5
--- /dev/null
+++ b/share/html/Helpers/Autocomplete/Principals
@@ -0,0 +1,142 @@
+%# BEGIN BPS TAGGED BLOCK {{{
+%#
+%# COPYRIGHT:
+%#
+%# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
+%#                                          <sales at bestpractical.com>
+%#
+%# (Except where explicitly superseded by other copyright notices)
+%#
+%#
+%# LICENSE:
+%#
+%# This work is made available to you under the terms of Version 2 of
+%# the GNU General Public License. A copy of that license should have
+%# been provided with this software, but in any event can be snarfed
+%# from www.gnu.org.
+%#
+%# This work 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 the GNU
+%# General Public License for more details.
+%#
+%# 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., 51 Franklin Street, Fifth Floor, Boston, MA
+%# 02110-1301 or visit their web page on the internet at
+%# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
+%#
+%#
+%# CONTRIBUTION SUBMISSION POLICY:
+%#
+%# (The following paragraph is not intended to limit the rights granted
+%# to you to modify and distribute this software under the terms of
+%# the GNU General Public License and is only of importance to you if
+%# you choose to contribute your changes and enhancements to the
+%# community by submitting them to Best Practical Solutions, LLC.)
+%#
+%# By intentionally submitting any modifications, corrections or
+%# derivatives to this work, or any other work intended for use with
+%# Request Tracker, to Best Practical Solutions, LLC, you confirm that
+%# you are the copyright holder for those contributions and you grant
+%# Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
+%# royalty-free, perpetual, license to use, copy, create derivative
+%# works based on those contributions, and sublicense and distribute
+%# those contributions and any derivatives thereof.
+%#
+%# END BPS TAGGED BLOCK }}}
+% $r->content_type('application/json; charset=utf-8');
+<% JSON( \@suggestions ) |n %>
+% $m->abort;
+<%ARGS>
+$term => undef
+$delim => undef
+$max => undef
+$privileged => undef
+$exclude => ''
+$include_nobody => 0
+$include_system => 0
+</%ARGS>
+<%INIT>
+my $return = 'EmailAddress';
+my $op = 'STARTSWITH';
+
+$m->abort unless defined $return
+             and defined $term
+             and length $term;
+
+# Use our delimeter if we have one
+if ( defined $delim and length $delim ) {
+    if ( $delim eq ',' ) {
+        $delim = qr/,\s*/;
+    } else {
+        $delim = qr/\Q$delim\E/;
+    }
+
+    # If the field handles multiple values, pop the last one off
+    $term = (split $delim, $term)[-1] if $term =~ $delim;
+}
+
+my $CurrentUser = $session{'CurrentUser'};
+
+# Require privileged users or overriding config
+$m->abort unless $CurrentUser->Privileged
+              or RT->Config->Get('AllowUserAutocompleteForUnprivileged');
+
+# the API wants a list of ids
+my @exclude = split /\s*,\s*/, $exclude;
+push @exclude, RT->SystemUser->id unless $include_system;
+push @exclude, RT->Nobody->id unless $include_nobody;
+
+$m->callback( CallbackName => 'ModifyMaxResults', max => \$max );
+$max //= 10;
+
+my $users = RT::Users->new($CurrentUser);
+$users->SimpleSearch( Privileged => $privileged,
+                      Return     => $return,
+                      Term       => $term,
+                      Max        => $max,
+                      Exclude    => \@exclude,
+                      # If an operator is provided, check against only
+                      # the returned field using that operator
+                      $op ? ( Fields => { $return => $op } ) : (),
+                    );
+
+(my $group_term = $term) =~ s/^\s*Group\s*:\s*//;
+my $groups = RT::Groups->new( $CurrentUser );
+$groups->RowsPerPage( $max );
+$groups->LimitToUserDefinedGroups();
+$groups->Limit(
+    FIELD           => 'Name',
+    OPERATOR        => $op,
+    VALUE           => $group_term,
+    CASESENSITIVE   => 0,
+);
+
+# Exclude groups we don't want
+foreach (@exclude) {
+    $groups->Limit(FIELD => 'id', VALUE => $_, OPERATOR => '!=', ENTRYAGGREGATOR => 'AND');
+}
+
+my @suggestions;
+
+while ( my $user = $users->Next ) {
+    my $suggestion = { id => $user->id, label => $user->Format, value => $user->$return, detail => loc('User') };
+    $m->callback( CallbackName => "ModifySuggestion", suggestion => $suggestion, user => $user );
+    push @suggestions, $suggestion;
+}
+
+while ( my $group = $groups->Next ) {
+    my $value = 'Group:' . $group->Name;
+
+    # if group name contains, say, a comma, then use its id instead to avoid
+    # parsing problems
+    $value = 'Group:' . $group->id if $delim && $value =~ $delim;
+
+    my $suggestion = { id => $group->Id, label => $group->Label, value => $value, detail => loc('Group') };
+    $m->callback( CallbackName => "ModifySuggestion", suggestion => $suggestion, group => $group );
+    push @suggestions, $suggestion;
+
+    last if @suggestions >= $max;
+}
+</%INIT>
diff --git a/share/static/css/base/forms.css b/share/static/css/base/forms.css
index 2584ee0..19fd770 100644
--- a/share/static/css/base/forms.css
+++ b/share/static/css/base/forms.css
@@ -268,6 +268,13 @@ ul.selectable a {
     font-size: .8em;
 }
 
+.ui-autocomplete .detail {
+    display: block;
+    font-size: .8em;
+    font-style: italic;
+    color: #999;
+}
+
 /* query builder */
 
 #formatbuttons {
diff --git a/share/static/js/autocomplete.js b/share/static/js/autocomplete.js
index cd8ab2b..3e2a3dc 100644
--- a/share/static/js/autocomplete.js
+++ b/share/static/js/autocomplete.js
@@ -5,7 +5,8 @@ window.RT.Autocomplete.Classes = {
     Users: 'user',
     Groups: 'group',
     Tickets: 'tickets',
-    Queues: 'queues'
+    Queues: 'queues',
+    Principals: 'principals'
 };
 
 window.RT.Autocomplete.bind = function(from) {
@@ -106,6 +107,12 @@ window.RT.Autocomplete.bind = function(from) {
                 else
                     rendered.html( item.html );
 
+                if (item.detail) {
+                    var detail = jQuery('<span class="detail">');
+                    detail.text(item.detail);
+                    rendered.append(detail);
+                }
+
                 return jQuery("<li/>")
                     .data( "item.autocomplete", item )
                     .append( rendered )

commit 44393ea4a186537b2b349b76fa99a6dffa16d1ff
Author: Shawn M Moore <shawn at bestpractical.com>
Date:   Wed May 17 21:38:42 2017 +0000

    Switch to Principals autocompleter for role input fields
    
    This lets users easily specify groups for watchers and custom roles,
    even on the ticket create page.
    
    The capability was previously there by specifying a group by ID, but
    that was a largely-unknown hidden feature.
    
    This fixes an inconsistency where managing custom role members on bulk
    update would accept multiple users; now it is consistent with core role
    fields that accept only a single member.
    
    Fixes: #32311

diff --git a/share/html/Asset/Elements/EditPeople b/share/html/Asset/Elements/EditPeople
index 658b436..41bf625 100644
--- a/share/html/Asset/Elements/EditPeople
+++ b/share/html/Asset/Elements/EditPeople
@@ -52,7 +52,7 @@
 <% loc($role) %>:
 </td>
 <td class="value" colspan="5">
-<& /Elements/EmailInput, Name => $role, Size => undef, Default => $ARGS{$role}, Autocomplete => 1 &>
+<& /Elements/EmailInput, Name => $role, Size => undef, Default => $ARGS{$role}, Autocomplete => 1, ($AssetObj->Role($role)->{Single} ? () : (AutocompleteType => 'Principals', AutocompleteMultiple => 1)) &>
 </td>
 </tr>
 % }
diff --git a/share/html/Asset/Elements/EditPeople b/share/html/Elements/MultiUserRoleInput
similarity index 82%
copy from share/html/Asset/Elements/EditPeople
copy to share/html/Elements/MultiUserRoleInput
index 658b436..f469e64 100644
--- a/share/html/Asset/Elements/EditPeople
+++ b/share/html/Elements/MultiUserRoleInput
@@ -2,7 +2,7 @@
 %#
 %# COPYRIGHT:
 %#
-%# This software is Copyright (c) 1996-2016 Best Practical Solutions, LLC
+%# This software is Copyright (c) 1996-2017 Best Practical Solutions, LLC
 %#                                          <sales at bestpractical.com>
 %#
 %# (Except where explicitly superseded by other copyright notices)
@@ -45,20 +45,16 @@
 %# those contributions and any derivatives thereof.
 %#
 %# END BPS TAGGED BLOCK }}}
-<table border="0" cellpadding="0" cellspacing="0">
-% for my $role ( $AssetObj->Roles ) {
-<tr class="asset-people-<% CSSClass($role) %>">
-<td class="label">
-<% loc($role) %>:
-</td>
-<td class="value" colspan="5">
-<& /Elements/EmailInput, Name => $role, Size => undef, Default => $ARGS{$role}, Autocomplete => 1 &>
-</td>
-</tr>
-% }
-
-</table>
-
-<%args>
-$AssetObj
-</%args>
+<& /Elements/EmailInput,
+    Name => $role->GroupType,
+    ($ShowEntryHint ? (EntryHint => $role->EntryHint) : ()),
+    Autocomplete => 1,
+    AutocompleteType => 'Principals',
+    AutocompleteMultiple => 1,
+    %ARGS,
+&>
+<%ARGS>
+$role
+$Ticket => undef
+$ShowEntryHint => 1
+</%ARGS>
diff --git a/share/html/Elements/QuickCreate b/share/html/Elements/QuickCreate
index 466565d..ab7648f 100644
--- a/share/html/Elements/QuickCreate
+++ b/share/html/Elements/QuickCreate
@@ -69,7 +69,7 @@
 </tr>
 <tr class="input-row">
     <td class="label"><&|/l&>Requestors</&>:</td>
-    <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &></td>
+    <td colspan="3" class="value"><& /Elements/EmailInput, Name => 'Requestors', AutocompleteType => 'Principals', Size => '40', Default => $args->{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &></td>
 </tr>
 <tr class="input-row">
 <td class="labeltop"><&|/l&>Content</&>:</td>
diff --git a/share/html/Search/Bulk.html b/share/html/Search/Bulk.html
index 326e830..2281203 100644
--- a/share/html/Search/Bulk.html
+++ b/share/html/Search/Bulk.html
@@ -83,17 +83,17 @@
 <label>(<input type="checkbox" class="checkbox" name="ForceOwnerChange"
 <% $ARGS{ForceOwnerChange} ? 'checked="checked"' : '' %> /> <&|/l&>Force change</&>)</label></td></tr>
 <tr><td class="label"> <&|/l&>Add Requestor</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "AddRequestor", Size=> 20, Default => $ARGS{AddRequestor} &> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "AddRequestor", Size=> 20, Default => $ARGS{AddRequestor}, AutocompleteType => 'Principals' &> </td></tr>
 <tr><td class="label"> <&|/l&>Remove Requestor</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "DeleteRequestor", Size=> 20, Default => $ARGS{DeleteRequestor} &> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "DeleteRequestor", Size=> 20, Default => $ARGS{DeleteRequestor}, AutocompleteType => 'Principals' &> </td></tr>
 <tr><td class="label"> <&|/l&>Add Cc</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "AddCc", Size=> 20, Default => $ARGS{AddCc} &> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "AddCc", Size=> 20, Default => $ARGS{AddCc}, AutocompleteType => 'Principals' &> </td></tr>
 <tr><td class="label"> <&|/l&>Remove Cc</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "DeleteCc", Size=> 20, Default => $ARGS{DeleteCc} &> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "DeleteCc", Size=> 20, Default => $ARGS{DeleteCc}, AutocompleteType => 'Principals' &> </td></tr>
 <tr><td class="label"> <&|/l&>Add AdminCc</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "AddAdminCc", Size=> 20, Default => $ARGS{AddAdminCc} &> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "AddAdminCc", Size=> 20, Default => $ARGS{AddAdminCc}, AutocompleteType => 'Principals' &> </td></tr>
 <tr><td class="label"> <&|/l&>Remove AdminCc</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "DeleteAdminCc", Size=> 20, Default => $ARGS{DeleteAdminCc} &> </td></tr>
+<td class="value"> <& /Elements/EmailInput, Name => "DeleteAdminCc", Size=> 20, Default => $ARGS{DeleteAdminCc}, AutocompleteType => 'Principals' &> </td></tr>
 
 % my $single_roles = RT::CustomRoles->new($session{CurrentUser});
 % $single_roles->LimitToSingleValue;
@@ -111,11 +111,11 @@
 % while (my $role = $multi_roles->Next) {
 <tr>
 <td class="label"> <&|/l, $role->Name &>Add [_1]</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "AddRT::CustomRole-" . $role->Id, Size=> 20, Default => $ARGS{"AddRT::CustomRole-" . $role->Id} &> </td>
+<td class="value"> <& /Elements/MultiUserRoleInput, role => $role, Name => "AddRT::CustomRole-" . $role->Id, Size => 20, Default => $ARGS{"AddRT::CustomRole-" . $role->Id}, AutocompleteMultiple => 0 &> </td>
 </tr>
 <tr>
 <td class="label"> <&|/l, $role->Name &>Remove [_1]</&>: </td>
-<td class="value"> <& /Elements/EmailInput, Name => "DeleteRT::CustomRole-" . $role->Id, Size=> 20, Default => $ARGS{"DeleteRT::CustomRole-" . $role->Id} &> </td>
+<td class="value"> <& /Elements/MultiUserRoleInput, role => $role, Name => "DeleteRT::CustomRole-" . $role->Id, Size => 20, Default => $ARGS{"DeleteRT::CustomRole-" . $role->Id}, AutocompleteMultiple => 0 &> </td>
 </tr>
 % }
 
diff --git a/share/html/SelfService/Create.html b/share/html/SelfService/Create.html
index 33faa5b..c8d0749 100644
--- a/share/html/SelfService/Create.html
+++ b/share/html/SelfService/Create.html
@@ -71,7 +71,7 @@
 <&|/l&>Requestors</&>:
 </td>
 <td class="value">
-<& /Elements/EmailInput, Name => 'Requestors', Size => '20', Default => $ARGS{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &>
+<& /Elements/EmailInput, Name => 'Requestors', Size => '20', Default => $ARGS{Requestors} || $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1, AutocompleteType => 'Principals' &>
 </td>
 </tr>
 <tr>
@@ -79,7 +79,7 @@
 <&|/l&>Cc</&>:
 </td>
 <td class="value">
-<& /Elements/EmailInput, Name => 'Cc', Size => '20', Default => $ARGS{Cc} || '', AutocompleteMultiple => 1 &>
+<& /Elements/EmailInput, Name => 'Cc', Size => '20', Default => $ARGS{Cc} || '', AutocompleteMultiple => 1, AutocompleteType => 'Principals' &>
 </td>
 </tr>
 <tr>
diff --git a/share/html/Ticket/Create.html b/share/html/Ticket/Create.html
index f8546da..037cbe1 100644
--- a/share/html/Ticket/Create.html
+++ b/share/html/Ticket/Create.html
@@ -148,7 +148,7 @@
 <&|/l&>Requestors</&>:
 </td>
 <td class="value" colspan="5">
-<& /Elements/EmailInput, Name => 'Requestors', Size => undef, Default => $ARGS{Requestors} // $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1 &>
+<& /Elements/EmailInput, Name => 'Requestors', Size => undef, Default => $ARGS{Requestors} // $session{CurrentUser}->EmailAddress, AutocompleteMultiple => 1, AutocompleteType => 'Principals' &>
 % $m->callback( CallbackName => 'AfterRequestors', QueueObj => $QueueObj, ARGSRef => \%ARGS );
 </td>
 </tr>
@@ -156,7 +156,7 @@
 <td class="label">
 <&|/l&>Cc</&>:
 </td>
-<td class="value" colspan="5"><& /Elements/EmailInput, Name => 'Cc', Size => undef, Default => $ARGS{Cc}, AutocompleteMultiple => 1 &></td>
+<td class="value" colspan="5"><& /Elements/EmailInput, Name => 'Cc', Size => undef, Default => $ARGS{Cc}, AutocompleteMultiple => 1, AutocompleteType => 'Principals' &></td>
 </tr>
 
 <tr>
@@ -172,7 +172,7 @@
 <td class="label">
 <&|/l&>Admin Cc</&>:
 </td>
-<td class="value" colspan="5"><& /Elements/EmailInput, Name => 'AdminCc', Size => undef, Default => $ARGS{AdminCc}, AutocompleteMultiple => 1 &></td>
+<td class="value" colspan="5"><& /Elements/EmailInput, Name => 'AdminCc', Size => undef, Default => $ARGS{AdminCc}, AutocompleteMultiple => 1, AutocompleteType => 'Principals' &></td>
 </tr>
 
 <tr>
@@ -191,7 +191,7 @@
 <td class="label">
 <% $role->Name %>:
 </td>
-<td class="value" colspan="5"><& /Elements/EmailInput, Name => $role->GroupType, Size => undef, Default => $ARGS{$role->GroupType}, AutocompleteMultiple => 1 &></td>
+<td class="value" colspan="5"><& /Elements/MultiUserRoleInput, role => $role, Default => $ARGS{$role->GroupType} &></td>
 </tr>
 
 % if ($role->EntryHint) {
diff --git a/share/html/Ticket/Elements/AddWatchers b/share/html/Ticket/Elements/AddWatchers
index 1cbb6fc..6bbf0a8 100644
--- a/share/html/Ticket/Elements/AddWatchers
+++ b/share/html/Ticket/Elements/AddWatchers
@@ -104,7 +104,7 @@
 <tr><td>
 <&/Elements/SelectWatcherType, Name => "WatcherTypeEmail" . $i, Queue => $Ticket->QueueObj &>
 </td><td>
-<& /Elements/EmailInput, Name => 'WatcherAddressEmail' . $i, Size => '20' &>
+<& /Elements/EmailInput, Name => 'WatcherAddressEmail' . $i, Size => '20', AutocompleteType => 'Principals' &>
 </td></tr>
 % }
 </table>

-----------------------------------------------------------------------


More information about the rt-commit mailing list