1 %# BEGIN BPS TAGGED BLOCK {{{
5 %# This software is Copyright (c) 1996-2011 Best Practical Solutions, LLC
6 %# <sales@bestpractical.com>
8 %# (Except where explicitly superseded by other copyright notices)
13 %# This work is made available to you under the terms of Version 2 of
14 %# the GNU General Public License. A copy of that license should have
15 %# been provided with this software, but in any event can be snarfed
18 %# This work is distributed in the hope that it will be useful, but
19 %# WITHOUT ANY WARRANTY; without even the implied warranty of
20 %# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
21 %# General Public License for more details.
23 %# You should have received a copy of the GNU General Public License
24 %# along with this program; if not, write to the Free Software
25 %# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
26 %# 02110-1301 or visit their web page on the internet at
27 %# http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
30 %# CONTRIBUTION SUBMISSION POLICY:
32 %# (The following paragraph is not intended to limit the rights granted
33 %# to you to modify and distribute this software under the terms of
34 %# the GNU General Public License and is only of importance to you if
35 %# you choose to contribute your changes and enhancements to the
36 %# community by submitting them to Best Practical Solutions, LLC.)
38 %# By intentionally submitting any modifications, corrections or
39 %# derivatives to this work, or any other work intended for use with
40 %# Request Tracker, to Best Practical Solutions, LLC, you confirm that
41 %# you are the copyright holder for those contributions and you grant
42 %# Best Practical Solutions, LLC a nonexclusive, worldwide, irrevocable,
43 %# royalty-free, perpetual, license to use, copy, create derivative
44 %# works based on those contributions, and sublicense and distribute
45 %# those contributions and any derivatives thereof.
47 %# END BPS TAGGED BLOCK }}}
57 my $LinkCallback = sub {
60 my $mode = $RT::Ticket::LINKTYPEMAP{$method}{Mode};
61 my $type = $RT::Ticket::LINKTYPEMAP{$method}{Type};
62 my $other_mode = ($mode eq "Target" ? "Base" : "Target");
63 my $mode_uri = $mode.'URI';
64 my $local_type = 'Local'.$mode;
69 $_->$mode_uri->Resolver->HREF,
71 ( $_->$mode_uri->IsLocal ? $_->$local_type : $_->$mode ),
73 } @{ $_[0]->Links($other_mode,$type)->ItemsArrayRef }
80 title => 'Queue id', # loc
81 value => sub { return $_[0]->Queue }
85 title => 'Queue', # loc
86 value => sub { return $_[0]->QueueObj->Name }
89 title => 'Owner', # loc
91 value => sub { return $_[0]->OwnerObj->Name }
94 title => 'Status', # loc
95 attribute => 'Status',
96 value => sub { return loc($_[0]->Status) }
99 title => 'Subject', # loc
100 attribute => 'Subject',
101 value => sub { return $_[0]->Subject || "(" . loc('No subject') . ")" }
104 title => 'Status', # loc
105 attribute => 'Status',
109 if ( my $count = $Ticket->HasUnresolvedDependencies ) {
110 if ( $Ticket->HasUnresolvedDependencies( Type => 'approval' )
111 or $Ticket->HasUnresolvedDependencies( Type => 'code' ) )
113 return \'<em>', loc('(pending approval)'), \'</em>';
116 my $Query = "DependedOnBy = " . $Ticket->id;
117 $Query .= " AND (" . join(" OR ", map { "Status = '$_'" } RT::Queue->ActiveStatusArray) . ")";
119 my $SearchURL = RT->Config->Get('WebPath') . '/Search/Results.html?' . $m->comp('/Elements/QueryString', Query => $Query);
121 return \'<a href="',$SearchURL,\'">', loc('(pending [quant,_1,other ticket])',$count), \'</a>';
125 return loc( $Ticket->Status );
131 title => 'Priority', # loc
132 attribute => 'Priority',
133 value => sub { return $_[0]->Priority }
136 title => 'InitialPriority', # loc
137 attribute => 'InitialPriority',
138 name => 'Initial Priority',
139 value => sub { return $_[0]->InitialPriority }
142 title => 'FinalPriority', # loc
143 attribute => 'FinalPriority',
144 name => 'Final Priority',
145 value => sub { return $_[0]->FinalPriority }
148 title => 'EffectiveId', # loc
149 attribute => 'EffectiveId',
150 value => sub { return $_[0]->EffectiveId }
153 title => 'Type', # loc
155 value => sub { return $_[0]->Type }
158 attribute => 'TimeWorked',
159 title => 'Time Worked', # loc
160 value => sub { return $_[0]->TimeWorked }
163 attribute => 'TimeLeft',
164 title => 'Time Left', # loc
165 value => sub { return $_[0]->TimeLeftAsString }
168 attribute => 'TimeEstimated',
169 title => 'Time Estimated', # loc
170 value => sub { return $_[0]->TimeEstimated }
173 title => 'Requestors', # loc
174 attribute => 'Requestor.EmailAddress',
175 value => sub { return $_[0]->Requestors->MemberEmailAddressesAsString }
179 attribute => 'Cc.EmailAddress',
180 value => sub { return $_[0]->Cc->MemberEmailAddressesAsString }
183 title => 'AdminCc', # loc
184 attribute => 'AdminCc.EmailAddress',
185 value => sub { return $_[0]->AdminCc->MemberEmailAddressesAsString }
188 title => 'Starts', # loc
189 attribute => 'Starts',
190 value => sub { return $_[0]->StartsObj->AgeAsString }
193 title => 'Started', # loc
194 attribute => 'Started',
195 value => sub { return $_[0]->StartedObj->AgeAsString }
198 title => 'Told', # loc
200 value => sub { return $_[0]->ToldObj->AgeAsString }
203 title => 'Due', # loc
206 my $date = $_[0]->DueObj;
207 # Highlight the date if it was due in the past, and it's still active
208 if ( $date && $date->Unix > 0 && $date->Diff < 0 && $_[0]->QueueObj->IsActiveStatus($_[0]->Status)) {
209 return (\'<span class="overdue">' , $date->AgeAsString , \'</span>');
211 return $date->AgeAsString;
215 ResolvedRelative => {
216 title => 'Resolved', # loc
217 attribute => 'Resolved',
218 value => sub { return $_[0]->ResolvedObj->AgeAsString }
221 title => 'Starts', # loc
222 attribute => 'Starts',
223 date => sub { return $_[0]->StartsObj },
224 value => sub { return $_[0]->StartsObj->AsString }
227 title => 'Started', # loc
228 attribute => 'Started',
229 date => sub { return $_[0]->StartedObj },
230 value => sub { return $_[0]->StartedObj->AsString }
233 title => 'Told', # loc
235 date => sub { return $_[0]->ToldObj },
236 value => sub { return $_[0]->ToldObj->AsString }
239 title => 'Due', # loc
241 date => sub { return $_[0]->DueObj },
242 value => sub { return $_[0]->DueObj->AsString }
245 title => 'Resolved', # loc
246 attribute => 'Resolved',
247 date => sub { return $_[0]->ResolvedObj },
248 value => sub { return $_[0]->ResolvedObj->AsString }
251 title => 'New messages', # loc
253 my $txn = $_[0]->SeenUpTo or return $_[0]->loc('No');
254 return \('<a href="'. RT->Config->Get('WebPath') .'/Ticket/Display.html?id='
255 . $_[0]->id .'#txn-'. $txn->id .'">'),
256 $_[0]->loc('New'), \'</a>';
260 title => 'Requestors', # loc
261 attribute => 'Requestor.EmailAddress',
264 my @requestors = $t->Requestors->MemberEmailAddresses;
265 for my $email (@requestors)
267 my %key = RT::Crypt::GnuPG::GetKeyInfo($email);
268 if (!defined $key{'info'}) {
269 $email .= loc(" (no pubkey!)");
271 elsif ($key{'info'}{'TrustLevel'} == 0) {
272 $email .= loc(" (untrusted!)");
275 return join ', ', @requestors;
279 title => 'Owner', # loc
280 attribute => 'Owner',
283 my $name = $t->OwnerObj->Name;
284 my %key = RT::Crypt::GnuPG::GetKeyInfo($t->OwnerObj->EmailAddress);
285 if (!defined $key{'info'}) {
286 $name .= ' '. loc("(no pubkey!)");
288 elsif ($key{'info'}{'TrustLevel'} == 0) {
289 $name .= ' '. loc("(untrusted!)");
296 # Everything from LINKTYPEMAP
298 $_ => { value => $LinkCallback->( $_ ) }
299 } keys %RT::Ticket::LINKTYPEMAP),
302 value => sub { return $_[1] % 2 ? 'oddline' : 'evenline' }
305 attribute => 'checkbox',
306 title => 'Update', # loc
308 value => sub { return \('<input type="checkbox" class="checkbox" name="UpdateTicket'.$_[0]->id.'" value="1" checked="checked" />') }
314 my $bookmark = $m->scomp( '/Ticket/Elements/Bookmark', id => $_[0]->id );
315 # the CollectionAsTable/Row template replaces newlines with <br>
316 $bookmark =~ s/\n//g;
323 title => 'Customer', #loc
324 attribute => 'Customer.Number', #title/attribute/name... what does it all mean?
328 foreach my $c (ticket_cust_resolvers($Ticket)) {
329 push @return, \'<A HREF="', $c->HREF, \'">',
338 # For future reference:
339 # hash key = name of the column in the format string
340 # (see /Search/Elements/BuildFormatString)
341 # title = displayed name in the table header
342 # attribute = the field to ORDER BY when sorting on this column
345 attribute => 'Customer.Agent',
349 foreach my $c (ticket_cust_resolvers($Ticket)) {
350 push @return, $c->AgentName, \'<BR>';
358 attribute => 'Customer.Class',
362 foreach my $c (ticket_cust_resolvers($Ticket)) {
363 push @return, $c->CustomerClass, \'<BR>';
375 foreach my $c (ticket_cust_resolvers($Ticket)) {
376 my @tags = sort { $a->{'name'} cmp $b->{'name'} }
378 foreach my $t (@tags) {
379 push @return, \'<SPAN style="background-color:#',
388 push @return, \'<BR>';
396 sub ticket_cust_resolvers {
398 my @Customers = @{ $Ticket->Customers->ItemsArrayRef };
399 return map $_->TargetURI->Resolver, @Customers;
402 # if no GPG support, then KeyOwnerName and KeyRequestors fall back to the regular
404 if (RT->Config->Get('GnuPG')->{'Enable'}) {
405 require RT::Crypt::GnuPG;
408 $COLUMN_MAP->{KeyOwnerName} = $COLUMN_MAP->{OwnerName};
409 $COLUMN_MAP->{KeyRequestors} = $COLUMN_MAP->{Requestors};
413 $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'Once', CallbackOnce => 1 );
414 # backward compatibility
415 $m->callback( COLUMN_MAP => $COLUMN_MAP, CallbackName => 'ColumnMap' );
416 return GetColumnMapEntry( Map => $COLUMN_MAP, Name => $Name, Attribute => $Attr );