1%# BEGIN BPS TAGGED BLOCK {{{ 2%# 3%# COPYRIGHT: 4%# 5%# This software is Copyright (c) 1996-2021 Best Practical Solutions, LLC 6%# <sales@bestpractical.com> 7%# 8%# (Except where explicitly superseded by other copyright notices) 9%# 10%# 11%# LICENSE: 12%# 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 16%# from www.gnu.org. 17%# 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. 22%# 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. 28%# 29%# 30%# CONTRIBUTION SUBMISSION POLICY: 31%# 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.) 37%# 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. 46%# 47%# END BPS TAGGED BLOCK }}} 48<%args> 49$id => undef 50</%args> 51<%init> 52my $Ticket; 53my @Actions; 54 55unless ($id) { 56 Abort('No ticket specified'); 57} 58 59if ($ARGS{'id'} eq 'new') { 60 # {{{ Create a new ticket 61 62 my $Queue = RT::Queue->new( $session{'CurrentUser'} ); 63 $Queue->Load($ARGS{'Queue'}); 64 unless ( $Queue->id ) { 65 Abort('Queue not found'); 66 } 67 68 unless ( $Queue->CurrentUserHasRight('CreateTicket') ) { 69 Abort('You have no permission to create tickets in that queue.'); 70 } 71 72 ($Ticket, @Actions) = CreateTicket( %ARGS ); 73 unless ( $Ticket->CurrentUserHasRight('ShowTicket') ) { 74 Abort("No permission to view newly created ticket #".$Ticket->id."."); 75 } 76} else { 77 $Ticket ||= LoadTicket($ARGS{'id'}); 78 79 $Ticket->Atomic(sub{ 80 $m->callback( CallbackName => 'BeforeProcessArguments', 81 TicketObj => $Ticket, 82 ActionsRef => \@Actions, ARGSRef => \%ARGS ); 83 84 if ( defined $ARGS{'Action'} ) { 85 if ($ARGS{'Action'} =~ /^(Steal|Delete|Take|SetTold)$/) { 86 my $action = $1; 87 my ($res, $msg) = $Ticket->$action(); 88 push(@Actions, $msg); 89 } 90 } 91 92 $m->callback(CallbackName => 'ProcessArguments', 93 Ticket => $Ticket, 94 ARGSRef => \%ARGS, 95 Actions => \@Actions); 96 97 push @Actions, 98 ProcessUpdateMessage( 99 ARGSRef => \%ARGS, 100 Actions => \@Actions, 101 TicketObj => $Ticket, 102 ); 103 104 #Process status updates 105 push @Actions, ProcessTicketWatchers(ARGSRef => \%ARGS, TicketObj => $Ticket ); 106 push @Actions, ProcessTicketBasics( ARGSRef => \%ARGS, TicketObj => $Ticket ); 107 push @Actions, ProcessTicketLinks( ARGSRef => \%ARGS, TicketObj => $Ticket ); 108 push @Actions, ProcessTicketDates( ARGSRef => \%ARGS, TicketObj => $Ticket ); 109 push @Actions, ProcessObjectCustomFieldUpdates(ARGSRef => \%ARGS, Object => $Ticket ); 110 push @Actions, ProcessTicketReminders( ARGSRef => \%ARGS, TicketObj => $Ticket ); 111 }); 112 113 unless ($Ticket->CurrentUserHasRight('ShowTicket')) { 114 if (@Actions) { 115 Abort("A change was applied successfully, but you no longer have permissions to view the ticket", Actions => \@Actions); 116 } else { 117 Abort("No permission to view ticket"); 118 } 119 } 120 if ( $ARGS{'MarkAsSeen'} ) { 121 $Ticket->SetAttribute( 122 Name => 'User-'. $Ticket->CurrentUser->id .'-SeenUpTo', 123 Content => $Ticket->LastUpdated, 124 ); 125 push @Actions, loc('Marked all messages as seen'); 126 } 127} 128 129$m->callback( 130 CallbackName => 'BeforeDisplay', 131 TicketObj => \$Ticket, 132 Actions => \@Actions, 133 ARGSRef => \%ARGS, 134); 135 136# This code does automatic redirection if any updates happen. 137 138if (@Actions) { 139 140 # We've done something, so we need to clear the decks to avoid 141 # resubmission on refresh. 142 # But we need to store Actions somewhere too, so we don't lose them. 143 my $key = Digest::MD5::md5_hex( rand(1024) ); 144 push @{ $session{"Actions"}->{$key} ||= [] }, @Actions; 145 $session{'i'}++; 146 my $url = RT->Config->Get('WebURL') . "m/ticket/show?id=" . $Ticket->id . "&results=" . $key; 147 $url .= '#' . $ARGS{Anchor} if $ARGS{Anchor}; 148 RT::Interface::Web::Redirect($url); 149} 150 151# If we haven't been passed in an Attachments object (through the precaching mechanism) 152# then we need to find one 153my $Attachments = $Ticket->Attachments; 154 155my %documents; 156while ( my $attach = $Attachments->Next() ) { 157 next unless ($attach->Filename()); 158 unshift( @{ $documents{ $attach->Filename } }, $attach ); 159} 160 161my $CustomFields = $Ticket->CustomFields; 162$m->callback( 163 CallbackName => 'MassageCustomFields', 164 Object => $Ticket, 165 CustomFields => $CustomFields, 166); 167 168my $print_value = sub { 169 my ($cf, $value) = @_; 170 my $linked = $value->LinkValueTo; 171 if ( defined $linked && length $linked ) { 172 my $linked = $m->interp->apply_escapes( $linked, 'h' ); 173 $m->out('<a href="'. $linked .'" target="_blank">'); 174 } 175 my $comp = "ShowCustomField". $cf->Type; 176 $m->callback( 177 CallbackName => 'ShowComponentName', 178 Name => \$comp, 179 CustomField => $cf, 180 Object => $Ticket, 181 ); 182 if ( $m->comp_exists( $comp ) ) { 183 $m->comp( $comp, Object => $value ); 184 } else { 185 $m->out( $m->interp->apply_escapes( $value->Content, 'h' ) ); 186 } 187 $m->out('</a>') if defined $linked && length $linked; 188 189 # This section automatically populates a div with the "IncludeContentForValue" for this custom 190 # field if it's been defined 191 if ( $cf->IncludeContentForValue ) { 192 my $vid = $value->id; 193 $m->out( '<div class="object_cf_value_include" id="object_cf_value_'. $vid .'">' ); 194 $m->print( loc("See also:") ); 195 $m->out( '<a href="'. $m->interp->apply_escapes($value->IncludeContentForValue, 'h') .'">' ); 196 $m->out( $m->interp->apply_escapes($value->IncludeContentForValue, 'h') ); 197 $m->out( qq{</a></div>\n} ); 198 $m->out( qq{<script><!--\njQuery('#object_cf_value_$vid').load(} ); 199 $m->out( $m->interp->apply_escapes($value->IncludeContentForValue, 'j') ); 200 $m->out( qq{);\n--></script>\n} ); 201 } 202}; 203 204</%init> 205<&| /m/_elements/wrapper, title => loc("#[_1]: [_2]", $Ticket->Id, $Ticket->Subject || '') &> 206<div id="ticket-show"> 207<& /m/_elements/ticket_menu, ticket => $Ticket &> 208 209 <&| /Widgets/TitleBox, title => loc('The Basics'), 210 class => 'ticket-info-basics', 211 &> 212 213 214 <div class="entry"> 215 <div class="label id"><&|/l&>Id</&>:</div> 216 <div class="value id"><%$Ticket->Id %></div> 217 </div> 218 <div class="entry"> 219 <div class="label status"><&|/l&>Status</&>:</div> 220 <div class="value status"><% loc($Ticket->Status) %></div> 221 </div> 222% if ($Ticket->TimeEstimated) { 223 <div class="entry"> 224 <div class="label time estimated"><&|/l&>Estimated</&>:</div> 225 <div class="value time estimated"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeEstimated &></div> 226 </div> 227% } 228% if ($Ticket->TimeWorked) { 229 <div class="entry"> 230 <div class="label time worked"><&|/l&>Worked</&>:</div> 231 <div class="value time worked"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeWorked &></div> 232 </div> 233% } 234% if ($Ticket->TimeLeft) { 235 <div class="entry"> 236 <div class="label time left"><&|/l&>Left</&>:</div> 237 <div class="value time left"><& /Ticket/Elements/ShowTime, minutes => $Ticket->TimeLeft &></div> 238 </div> 239% } 240 <div class="entry"> 241 <div class="label priority"><&|/l&>Priority</&>:</div> 242 <div class="value priority"><& /Ticket/Elements/ShowPriority, Ticket => $Ticket &></div> 243 </div> 244 <div class="entry"> 245 <div class="label queue"><&|/l&>Queue</&>:</div> 246 <div class="value queue"><& /Ticket/Elements/ShowQueue, QueueObj => $Ticket->QueueObj &></div> 247 </div> 248 <div class="entry"> 249 <div class="label bookmark"><&|/l&>Bookmark</&>:</div> 250 <div class="value bookmark"><& /Ticket/Elements/Bookmark, id => $Ticket->id &></div> 251 </div> 252 </&> 253 254% if ($CustomFields->Count) { 255 <&| /Widgets/TitleBox, title => loc('Custom Fields'), 256 class => 'ticket-info-cfs', 257 &> 258 259% while ( my $CustomField = $CustomFields->Next ) { 260% my $Values = $Ticket->CustomFieldValues( $CustomField->Id ); 261% my $count = $Values->Count; 262 <div class="entry" id="CF-<%$CustomField->id%>-ShowRow"> 263 <div class="label"><% $CustomField->Name %>:</div> 264 <div class="value"> 265% unless ( $count ) { 266<i><&|/l&>(no value)</&></i> 267% } elsif ( $count == 1 ) { 268% $print_value->( $CustomField, $Values->First ); 269% } else { 270<ul> 271% while ( my $Value = $Values->Next ) { 272<li> 273% $print_value->( $CustomField, $Value ); 274</li> 275% } 276</ul> 277% } 278 </div> 279 </div> 280% } 281 282</&> 283% } 284 285 <&| /Widgets/TitleBox, title => loc('People'), class => 'ticket-info-people' &> 286 287 288 <div class="entry"> 289 <div class="label"><&|/l&>Owner</&>:</div> 290 <div class="value"><& /Elements/ShowUser, User => $Ticket->OwnerObj, Ticket => $Ticket, Link => 0 &> 291 </div> 292 </div> 293 <div class="entry"> 294 <div class="label"><&|/l&>Requestors</&>:</div> 295 <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Requestors, Ticket => $Ticket, Link => 0 &></div> 296 </div> 297 <div class="entry"> 298 <div class="label"><&|/l&>Cc</&>:</div> 299 <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->Cc, Ticket => $Ticket, Link => 0 &></div> 300 </div> 301 <div class="entry"> 302 <div class="label"><&|/l&>AdminCc</&>:</div> 303 <div class="value"><& /Ticket/Elements/ShowGroupMembers, Group => $Ticket->AdminCc, Ticket => $Ticket, Link => 0 &></div> 304 </div> 305 306 </&> 307 308% if (keys %documents) { 309<&| /Widgets/TitleBox, title => loc('Attachments'), 310 title_class=> 'inverse', 311 class => 'ticket-info-attachments', 312 color => "#336699" &> 313 314% foreach my $key (keys %documents) { 315 316<%$key%><br /> 317<ul> 318% foreach my $rev (@{$documents{$key}}) { 319% if ($rev->ContentLength) { 320<li><font size="-2"> 321% if (my $url = RT->System->ExternalStorageURLFor($rev)) { 322<a href="<%$url%>"> 323% } else { 324<a href="<%RT->Config->Get('WebPath')%>/Ticket/Attachment/<%$rev->TransactionId%>/<%$rev->Id%>/<%$rev->Filename | un %>"> 325% } 326<&|/l, $rev->CreatedAsString, $rev->FriendlyContentLength, $rev->CreatorObj->Name &>[_1] ([_2]) by [_3]</&> 327</a> 328</font></li> 329% } 330% } 331</ul> 332 333% } 334</&> 335 336% } 337% # too painful to deal with reminders 338% if ( 0 && RT->Config->Get('EnableReminders') ) { 339 <&|/Widgets/TitleBox, title => loc("Reminders"), 340 class => 'ticket-info-reminders', 341 &> 342 <div class="entry"><div 343 <form action="<%RT->Config->Get('WebPath')%>/Ticket/Display.html" method="post"> 344 <& /Ticket/Elements/Reminders, Ticket => $Ticket, ShowCompleted => 0 &> 345 <div align="right"><input type="submit" class="button" value="<&|/l&>Save</&>" /></div> 346 </form> 347 </div></div> 348 </&> 349% } 350 351 <&| /Widgets/TitleBox, title => loc("Dates"), 352 class => 'ticket-info-dates', 353 &> 354 355 356 <div class="entry"> 357 <div class="label date created"><&|/l&>Created</&>:</div> 358 <div class="value date created"><% $Ticket->CreatedObj->AsString %></div> 359 </div> 360 <div class="entry"> 361 <div class="label date starts"><&|/l&>Starts</&>:</div> 362 <div class="value date starts"><% $Ticket->StartsObj->AsString %></div> 363 </div> 364 <div class="entry"> 365 <div class="label date started"><&|/l&>Started</&>:</div> 366 <div class="value date started"><% $Ticket->StartedObj->AsString %></div> 367 </div> 368 <div class="entry"> 369 <div class="label date told"><&|/l&>Last Contact</&>:</div> 370 <div class="value date told"><% $Ticket->ToldObj->AsString %></div> 371 </div> 372 <div class="entry"> 373 <div class="label date due"><&|/l&>Due</&>:</div> 374% my $due = $Ticket->DueObj; 375% if ( $due && $due->IsSet && $due->Diff < 0 && $Ticket->QueueObj->IsActiveStatus($Ticket->Status) ) { 376 <div class="value date due"><span class="overdue"><% $due->AsString %></span></div> 377% } else { 378 <div class="value date due"><% $due->AsString %></div> 379% } 380 </div> 381 <div class="entry"> 382 <div class="label date resolved"><&|/l&>Closed</&>:</div> 383 <div class="value date resolved"><% $Ticket->ResolvedObj->AsString %></div> 384 </div> 385 <div class="entry"> 386 <div class="label date updated"><&|/l&>Updated</&>:</div> 387% my $UpdatedString = $Ticket->LastUpdated ? loc("[_1] by [_2]", $Ticket->LastUpdatedAsString, $Ticket->LastUpdatedByObj->Name) : loc("Never"); 388 <div class="value date updated"><% $UpdatedString | h %></div> 389 </div> 390 391 </&> 392 393 <&| /Widgets/TitleBox, title => loc('Links'), class => 'ticket-info-links' &> 394 395 <div class="entry"> 396 <div class="label"><% loc('Depends on')%>:</div> 397 <div class="value"> 398 399<%PERL> 400my ( @active, @inactive, @not_tickets ); 401for my $link ( @{ $Ticket->DependsOn->ItemsArrayRef } ) { 402 my $target = $link->TargetObj; 403 if ( $target && $target->isa('RT::Ticket') ) { 404 if ( $target->QueueObj->IsInactiveStatus( $target->Status ) ) { 405 push( @inactive, $link->TargetURI ); 406 } 407 else { 408 push( @active, $link->TargetURI ); 409 } 410 } 411 elsif ( not (UNIVERSAL::isa($link->TargetObj, 'RT::Article') && $link->TargetObj->Disabled) ) { 412 push( @not_tickets, $link->TargetURI ); 413 } 414} 415</%PERL> 416 417 418<ul> 419% for my $Link (@not_tickets, @active, @inactive) { 420<li><& /Elements/ShowLink, URI => $Link &></li> 421% } 422</ul> 423 </div> 424 </div> 425 <div class="entry"> 426 <div class="label"><% loc('Depended on by')%>:</div> 427 <div class="value"> 428<ul> 429% while (my $Link = $Ticket->DependedOnBy->Next) { 430% next if UNIVERSAL::isa($Link->BaseObj, 'RT::Article') && $Link->BaseObj->Disabled; 431<li><& /Elements/ShowLink, URI => $Link->BaseURI &></li> 432% } 433</ul> 434 </div> 435 </div> 436 <div class="entry"> 437 <div class="label"><% loc('Parents') %>:</div> 438 <div class="value"><& /Elements/ShowLinksOfType, Object => $Ticket, Type => 'MemberOf' &></div> 439 </div> 440 <div class="entry"> 441 <div class="label"><% loc('Children')%>:</div> 442 <div class="value"><& /Elements/ShowLinksOfType, Object => $Ticket, Type => 'Members' &></div> 443 </div> 444 <div class="entry"> 445 <div class="label"><% loc('Refers to')%>:</div> 446 <div class="value"> 447<ul> 448% while (my $Link = $Ticket->RefersTo->Next) { 449% next if UNIVERSAL::isa($Link->TargetObj, 'RT::Article') && $Link->TargetObj->Disabled; 450<li><& /Elements/ShowLink, URI => $Link->TargetURI &></li> 451% } 452</ul> 453 </div> 454 </div> 455 <div class="entry"> 456 <div class="label"><% loc('Referred to by')%>:</div> 457 <div class="value"> 458 <ul> 459% while (my $Link = $Ticket->ReferredToBy->Next) { 460% next if UNIVERSAL::isa($Link->BaseObj, 'RT::Article') && $Link->BaseObj->Disabled; 461% next if (UNIVERSAL::isa($Link->BaseObj, 'RT::Ticket') && $Link->BaseObj->__Value('Type') eq 'reminder'); 462<li><& /Elements/ShowLink, URI => $Link->BaseURI &></li> 463% } 464</ul> 465 </div> 466 </div> 467 </&> 468</div> 469</&> 470