Hi Ken,
Indeed, I’ve updated this patch for a local installation of RT 5.0.4:
diff --git a/rt/share/html/Elements/AddLinks b/rt/share/html/Elements/AddLinks
index 4fd52791..cdf2c24a 100644
--- a/rt/share/html/Elements/AddLinks
+++ b/rt/share/html/Elements/AddLinks
@@ -55,8 +55,9 @@ my $id = ($Object and $Object->id)
? $Object->id
: "new";
-my $exclude = qq| data-autocomplete="Tickets" data-autocomplete-multiple="1"|;
-$exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id;
+my $exclude = qq| data-autocomplete="TicketsAssets" data-autocomplete-multiple="1"|;
+my @excludes;
+push @excludes, $id if $Object->id;
</%init>
% if (ref($Object) eq 'RT::Ticket') {
<i><&|/l&>Enter tickets or URIs to link tickets to. Separate multiple entries with spaces.</&>
@@ -76,24 +77,54 @@ $exclude .= qq| data-autocomplete-exclude="$id"| if $Object->id;
<&| /Elements/LabeledValue, RawLabel => $m->scomp('ShowRelationLabel', Object => $Object, Label => loc('Depends on'), Relation => 'DependsOn') &>
- <input type="text" class="form-control" name="<%$id%>-DependsOn" value="<% $ARGSRef->{"$id-DependsOn"} || '' %>" <% $exclude |n%>/>
+% my @excludes_dependson;
+% while (my $link = $Object->DependsOn->Next) {
+% push @excludes_dependson, ((UNIVERSAL::isa($link->TargetObj, 'RT::Asset') ? 'asset:' : '') . $link->TargetObj->id) if $link->TargetObj;
+% }
+% my $exclude_dependson = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_dependson)). '"' if @excludes_dependson || @excludes;
+ <input type="text" class="form-control" name="<%$id%>-DependsOn" value="<% $ARGSRef->{"$id-DependsOn"} || '' %>" <% $exclude_dependson |n%>/>
</&>
<&| /Elements/LabeledValue, RawLabel => $m->scomp('ShowRelationLabel', Object => $Object, Label => loc('Depended on by'), Relation => 'DependedOnBy') &>
- <input type="text" class="form-control" name="DependsOn-<%$id%>" value="<% $ARGSRef->{"DependsOn-$id"} || '' %>" <% $exclude |n%>/>
+% my @excludes_dependedonby;
+% while (my $link = $Object->DependedOnBy->Next) {
+% push @excludes_dependedonby, ((UNIVERSAL::isa($link->BaseObj, 'RT::Asset') ? 'asset:' : '') . $link->BaseObj->id) if $link->BaseObj;
+% }
+% my $exclude_dependonby = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_dependedonby)). '"' if @excludes_dependedonby || @excludes;
+ <input type="text" class="form-control" name="DependsOn-<%$id%>" value="<% $ARGSRef->{"DependsOn-$id"} || '' %>" <% $exclude_dependonby |n%>/>
</&>
<&| /Elements/LabeledValue, RawLabel => $m->scomp('ShowRelationLabel', Object => $Object, Label => loc('Parents'), Relation => 'Parents') &>
- <input type="text" class="form-control" name="<%$id%>-MemberOf" value="<% $ARGSRef->{"$id-MemberOf"} || '' %>" <% $exclude |n%>/>
+% my @excludes_memberof;
+% while (my $link = $Object->MemberOf->Next) {
+% push @excludes_memberof, ((UNIVERSAL::isa($link->TargetObj, 'RT::Asset') ? 'asset:' : '') . $link->TargetObj->id) if $link->TargetObj;
+% }
+% my $exclude_memberof = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_memberof)). '"' if @excludes_memberof || @excludes;
+ <input type="text" class="form-control" name="<%$id%>-MemberOf" value="<% $ARGSRef->{"$id-MemberOf"} || '' %>" <% $exclude_memberof |n%>/>
</&>
<&| /Elements/LabeledValue, RawLabel => $m->scomp('ShowRelationLabel', Object => $Object, Label => loc('Children'), Relation => 'Children') &>
- <input type="text" class="form-control" name="MemberOf-<%$id%>" value="<% $ARGSRef->{"MemberOf-$id"} || '' %>" <% $exclude |n%>/>
+% my @excludes_members;
+% while (my $link = $Object->Members->Next) {
+% push @excludes_members, ((UNIVERSAL::isa($link->BaseObj, 'RT::Asset') ? 'asset:' : '') . $link->BaseObj->id) if $link->BaseObj;
+% }
+% my $exclude_members = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_members)). '"' if @excludes_members || @excludes;
+ <input type="text" class="form-control" name="MemberOf-<%$id%>" value="<% $ARGSRef->{"MemberOf-$id"} || '' %>" <% $exclude_members |n%>/>
</&>
<&| /Elements/LabeledValue, RawLabel => $m->scomp('ShowRelationLabel', Object => $Object, Label => loc('Refers to'), Relation => 'RefersTo') &>
- <input type="text" class="form-control" name="<%$id%>-RefersTo" value="<% $ARGSRef->{"$id-RefersTo"} || '' %>" <% $exclude |n%>/>
+% my @excludes_refersto;
+% while (my $link = $Object->RefersTo->Next) {
+% push @excludes_refersto, ((UNIVERSAL::isa($link->TargetObj, 'RT::Asset') ? 'asset:' : '') . $link->TargetObj->id) if $link->TargetObj;
+% }
+% my $exclude_refersto = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_refersto)). '"' if @excludes_refersto || @excludes;
+ <input type="text" class="form-control" name="<%$id%>-RefersTo" value="<% $ARGSRef->{"$id-RefersTo"} || '' %>" <% $exclude_refersto |n%>/>
</&>
<&| /Elements/LabeledValue, RawLabel => $m->scomp('ShowRelationLabel', Object => $Object, Label => loc('Referred to by'), Relation => 'ReferredToBy') &>
- <input type="text" class="form-control" name="RefersTo-<%$id%>" value="<% $ARGSRef->{"RefersTo-$id"} || '' %>" <% $exclude |n%>/>
+% my @excludes_referredtoby;
+% while (my $link = $Object->ReferredToBy->Next) {
+% push @excludes_referredtoby, ((UNIVERSAL::isa($link->BaseObj, 'RT::Asset') ? 'asset:' : '') . $link->BaseObj->id) if $link->BaseObj;
+% }
+% my $exclude_referredtoby = $exclude . ' data-autocomplete-exclude="' .join(' ', (@excludes, @excludes_referredtoby)). '"' if @excludes_referredtoby || @excludes;
+ <input type="text" class="form-control" name="RefersTo-<%$id%>" value="<% $ARGSRef->{"RefersTo-$id"} || '' %>" <% $exclude_referredtoby |n%>/>
</&>
<& /Elements/EditCustomFields,
diff --git a/rt/share/html/Helpers/Autocomplete/Assets b/rt/share/html/Helpers/Autocomplete/Assets
index ef93af5a..4a26cb29 100644
--- a/rt/share/html/Helpers/Autocomplete/Assets
+++ b/rt/share/html/Helpers/Autocomplete/Assets
@@ -52,10 +52,12 @@
<%ARGS>
$term => undef
$max => 10
-$op => 'STARTSWITH'
+$exclude => ''
+$op => 'LIKE'
$right => undef
$return => 'id'
$queue => undef
+$return_suggestions => 0
</%ARGS>
<%INIT>
@@ -68,7 +70,7 @@ $m->abort unless defined $return
and length $term;
# Sanity check the operator
-$op = 'STARTSWITH' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i;
+$op = 'LIKE' unless $op =~ /^(?:LIKE|(?:START|END)SWITH|=|!=)$/i;
my $assets = RT::Assets->new( $session{CurrentUser} );
@@ -81,10 +83,16 @@ $assets->Limit(
CASESENSITIVE => 0,
);
+# Exclude assets we don't want
+foreach (split /\s*,\s*/, $exclude) {
+ $assets->Limit(FIELD => 'id', VALUE => $_, OPERATOR => '!=', ENTRYAGGREGATOR => 'AND', SUBCLAUSE => 'excludeautocomplete');
+}
+
my @suggestions;
while (my $a = $assets->Next) {
next if $right and not $a->CurrentUserHasRight($right);
my $value = $a->$return;
push @suggestions, { label => $a->Name, value => $value };
}
+return @suggestions if $return_suggestions;
</%INIT>
diff --git a/rt/share/html/Helpers/Autocomplete/Tickets b/rt/share/html/Helpers/Autocomplete/Tickets
index 8e9462ce..7429b031 100644
--- a/rt/share/html/Helpers/Autocomplete/Tickets
+++ b/rt/share/html/Helpers/Autocomplete/Tickets
@@ -54,6 +54,7 @@ $term => undef
$max => undef
$exclude => ''
$limit => undef
+$return_suggestions => 0
</%ARGS>
<%INIT>
# Only allow certain return fields
@@ -64,6 +65,7 @@ $m->abort unless defined $return
and defined $term
and length $term;
+
my $CurrentUser = $session{'CurrentUser'};
# Require privileged users
@@ -110,5 +112,6 @@ while ( my $ticket = $tickets->Next ) {
my $formatted = loc("#[_1]: [_2]", $ticket->Id, $ticket->Subject);
push @suggestions, { label => $formatted, value => $ticket->$return };
}
+return @suggestions if $return_suggestions;
</%INIT>
diff --git a/rt/share/html/Helpers/Autocomplete/TicketsAssets b/rt/share/html/Helpers/Autocomplete/TicketsAssets
new file mode 100644
index 00000000..cb69c712
--- /dev/null
+++ b/rt/share/html/Helpers/Autocomplete/TicketsAssets
@@ -0,0 +1,26 @@
+% $r->content_type('application/json; charset=utf-8');
+<% JSON( \@suggestions ) |n %>
+% $m->abort;
+<%args>
+$return => ''
+$term => undef
+$max => undef
+$exclude => ''
+</%args>
+<%init>
+my @suggestions;
+my @excludes;
+
+(my $prev, my $type, $term) = $term =~ /^((?:(asset:)?\d+\s+)*)(.*)/;
+@excludes = split ' ', $prev if $prev;
+push @excludes, split ' ', $exclude if $exclude;
+
+if ($term =~ /^asset:./) {
+ my $exclude_assets = join(',', map(/(\d+)/, grep(/^asset:\d+$/, @excludes)));
+ @suggestions = $m->comp('Assets', term => substr($term, 6), max => $max, exclude => $exclude_assets, return_suggestions => 1);
+ @suggestions = map { {id => $_->{id}, label => $_->{label}, value => 'asset:' . $_->{value}} } @suggestions;
+} else {
+ my $exclude_tickets = join(' ', grep(/^\d+$/, @excludes));
+ @suggestions = $m->comp('Tickets', return => $return, term => $term, max => $max, exclude => $exclude_tickets, return_suggestions => 1);
+}
+</%init>
diff --git a/rt/share/static/js/autocomplete.js b/rt/share/static/js/autocomplete.js
index c967050d..ed7bbafc 100644
--- a/rt/share/static/js/autocomplete.js
+++ b/rt/share/static/js/autocomplete.js
@@ -9,6 +9,7 @@ window.RT.Autocomplete.Classes = {
Queues: 'queues',
Articles: 'articles',
Assets: 'assets',
+ TicketsAssets: 'tickets-assets',
Principals: 'principals'
};
@@ -150,7 +151,7 @@ window.RT.Autocomplete.bind = function(from) {
}
if (input.is('[data-autocomplete-multiple]')) {
- if ( what != 'Tickets' ) {
+ if ( what != 'Tickets' && what != 'TicketsAssets' ) {
queryargs.push("delim=,");
}
@@ -160,22 +161,23 @@ window.RT.Autocomplete.bind = function(from) {
}
options.select = function(event, ui) {
- var terms = this.value.split(what == 'Tickets' ? /\s+/ : /,\s*/);
+ var terms = this.value.split((what == 'Tickets' || what == 'TicketsAssets') ? /\s+/ : /,\s*/);
terms.pop(); // remove current input
- if ( what == 'Tickets' ) {
+ if ( what == 'Tickets' || what == 'TicketsAssets' ) {
// remove non-integers in case subject search with spaces in (like "foo bar")
var new_terms = [];
for ( var i = 0; i < terms.length; i++ ) {
- if ( terms[i].match(/\D/) ) {
- break; // Items after the first non-integers are all parts of search string
+ if ( terms[i].match(/^(?:asset:)?\d+$/) ) {
+ new_terms.push(terms[i]);
+ continue;
}
- new_terms.push(terms[i]);
+ break; // Items after the first non-integers / asset:integer are all parts of search string
}
terms = new_terms;
}
terms.push( ui.item.value ); // add selected item
terms.push(''); // add trailing delimeter so user can input another value directly
- this.value = terms.join(what == 'Tickets' ? ' ' : ", ");
+ this.value = terms.join((what == 'Tickets' || what == 'TicketsAssets') ? ' ' : ", ");
jQuery(this).change();
return false;