1 package FS::TicketSystem::RT_Internal;
4 use vars qw( @ISA $DEBUG $me );
8 use FS::CGI qw(popurl);
9 use FS::TicketSystem::RT_Libs;
11 @ISA = qw( FS::TicketSystem::RT_Libs );
14 $me = '[FS::TicketSystem::RT_Internal]';
16 sub sql_num_customer_tickets {
17 "( select count(*) from Tickets
18 join Links on ( Tickets.id = Links.LocalBase )
19 where ( Status = 'new' or Status = 'open' or Status = 'stalled' )
20 and Target = 'freeside://freeside/cust_main/' || custnum
26 if ( $RT::URI::freeside::URL ) {
27 $RT::URI::freeside::URL. '/rt/';
29 'http://you_need_to_set_RT_URI_freeside_URL_in_SiteConfig.pm/';
34 #ShowConfigTab ModifySelf
36 my( $self, $session, $right ) = @_;
38 return '' unless FS::Conf->new->config('ticket_system');
40 $session = $self->session($session);
42 #warn "$me access_right: CurrentUser ". $session->{'CurrentUser'}. ":\n".
43 # ( $DEBUG>1 ? Dumper($session->{'CurrentUser'}) : '' )
46 $session->{'CurrentUser'}->HasRight( Right => $right,
47 Object => $RT::System );
51 my( $self, $session ) = @_;
53 if ( $session && $session->{'CurrentUser'} ) { # does this even work?
54 warn "$me session: using existing session and CurrentUser: \n".
55 Dumper($session->{'CurrentUser'})
58 warn "$me session: loading session and CurrentUser\n" if $DEBUG > 1;
59 $session = $self->_web_external_auth($session);
71 # this part only needs to be done once
72 warn "$me init: loading RT libraries\n" if $DEBUG;
74 use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
77 #for web external auth...
78 use RT::Interface::Web;
82 warn "$me init: loading RT config\n" if $DEBUG;
85 eval 'RT::LoadConfig();';
92 # this needs to be done on each fork
93 warn "$me init: initializing RT\n" if $DEBUG;
97 eval 'RT::Init("NoSignalHandlers"=>1);';
101 warn "$me init: complete" if $DEBUG;
104 =item customer_tickets CUSTNUM [ LIMIT ] [ PRIORITYVALUE ]
106 Replacement for the one in RT_External so that we can access custom fields
111 # create an RT::Tickets object for a specified custnum or svcnum
113 sub _tickets_search {
114 my ( $self, $type, $number, $limit, $priority ) = @_;
116 $type =~ /^Customer|Service$/ or die "invalid type: $type";
117 $number =~ /^\d+$/ or die "invalid custnum/svcnum: $number";
118 $limit =~ /^\d+$/ or die "invalid limit: $limit";
120 my $session = $self->session();
121 my $CurrentUser = $session->{CurrentUser}
122 or die "unable to create an RT session";
124 my $Tickets = RT::Tickets->new($CurrentUser);
126 # "Customer.number" searches tickets linked via cust_svc also
127 my $rtql = "$type.number = $number";
129 if ( defined( $priority ) ) {
130 my $custom_priority = FS::Conf->new->config('ticket_system-custom_priority_field');
131 if ( length( $priority ) ) {
132 $rtql .= " AND CF.{$custom_priority} = '$priority'";
135 $rtql .= " AND CF.{$custom_priority} IS NULL";
140 join(' OR ', map { "Status = '$_'" } $self->statuses) .
143 warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG;
144 $Tickets->FromSQL($rtql);
146 $Tickets->RowsPerPage($limit);
147 warn "\n\n" . $Tickets->BuildSelectQuery . "\n\n" if $DEBUG > 1;
152 sub href_customer_tickets {
153 my ($self, $custnum) = (shift, shift);
154 if ($custnum =~ /^(\d+)$/) {
155 return $self->href_search_tickets("Customer.number = $custnum", @_);
157 warn "bad custnum $custnum"; '';
160 sub href_service_tickets {
161 my ($self, $svcnum) = (shift, shift);
162 if ($svcnum =~ /^(\d+)$/ ) {
163 return $self->href_search_tickets("Service.number = $svcnum", @_);
165 warn "bad svcnum $svcnum"; '';
168 sub customer_tickets {
170 my $Tickets = $self->_tickets_search('Customer', @_);
172 my $conf = FS::Conf->new;
174 $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
177 { FIELD => 'Priority', ORDER => $priority_order },
178 { FIELD => 'Id', ORDER => 'DESC' },
181 $Tickets->OrderByCols(@order_by);
184 while ( my $t = $Tickets->Next ) {
185 push @tickets, _ticket_info($t);
191 sub num_customer_tickets {
192 my ( $self, $custnum, $priority ) = @_;
193 $self->_tickets_search('Customer', $custnum, 0, $priority)->CountAll;
196 sub service_tickets {
198 my $Tickets = $self->_tickets_search('Service', @_);
200 my $conf = FS::Conf->new;
202 $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
205 { FIELD => 'Priority', ORDER => $priority_order },
206 { FIELD => 'Id', ORDER => 'DESC' },
209 $Tickets->OrderByCols(@order_by);
212 while ( my $t = $Tickets->Next ) {
213 push @tickets, _ticket_info($t);
220 # Takes an RT::Ticket; returns a hashref of the ticket's fields, including
221 # custom fields. Also returns custom and selfservice priority values as
222 # _custom_priority and _selfservice_priority.
225 my $custom_priority =
226 FS::Conf->new->config('ticket_system-custom_priority_field') || '';
227 my $ss_priority = selfservice_priority();
230 foreach my $name ( $t->ReadableAttributes ) {
231 # lowercase names, and skip attributes with non-scalar values
232 $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
234 $ticket_info{'owner'} = $t->OwnerObj->Name;
235 $ticket_info{'queue'} = $t->QueueObj->Name;
236 foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
237 my $name = 'CF.{'.$CF->Name.'}';
238 $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
240 # make this easy to find
241 if ( $custom_priority ) {
242 $ticket_info{'content'} = $ticket_info{"CF.{$custom_priority}"};
244 if ( $ss_priority ) {
245 $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
248 map { $_->Target =~ /cust_svc\/(\d+)/; $1 }
249 @{ $t->Services->ItemsArrayRef }
251 $ticket_info{'svcnums'} = $svcnums;
253 return \%ticket_info;
256 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
258 Class method. Creates a ticket. If there is an error, returns the scalar
259 error, otherwise returns the newly created RT::Ticket object.
261 Accepts the following options:
275 Requestor email address or arrayref of addresses
279 Cc: email address or arrayref of addresses
287 MIME type to use for message. Defaults to text/plain. Specifying text/html
288 can be useful to use HTML markup in message.
292 Customer number (see L<FS::cust_main>) to associate with ticket.
296 Service number (see L<FS::cust_svc>) to associate with ticket. Will also
297 associate the customer who has this service (unless the service is unlinked).
304 my($self, $session, %param) = @_;
306 $session = $self->session($session);
308 my $Queue = RT::Queue->new($session->{'CurrentUser'});
309 $Queue->Load( $param{'queue'} );
311 my $req = ref($param{'requestor'})
312 ? $param{'requestor'}
313 : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
315 my $cc = ref($param{'cc'})
317 : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
319 my $mimeobj = MIME::Entity->build(
320 'Data' => $param{'message'},
321 'Type' => ( $param{'mime_type'} || 'text/plain' ),
325 'Queue' => $Queue->Id,
326 'Subject' => $param{'subject'},
329 'MIMEObj' => $mimeobj,
331 warn Dumper(\%ticket) if $DEBUG > 1;
333 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
334 my( $id, $Transaction, $ErrStr );
337 ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
339 return $ErrStr if $id == 0;
341 warn "ticket got id $id\n" if $DEBUG;
343 #XXX check errors adding custnum/svcnum links (put it in a transaction)...
344 # but we do already know they're good
346 if ( $param{'custnum'} ) {
347 my( $val, $msg ) = $Ticket->_AddLink(
348 'Type' => 'MemberOf',
349 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
353 if ( $param{'svcnum'} ) {
354 my( $val, $msg ) = $Ticket->_AddLink(
355 'Type' => 'MemberOf',
356 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
363 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
365 Class method. Retrieves a ticket. If there is an error, returns the scalar
366 error. Otherwise, currently returns a slightly tricky data structure containing
367 the ticket's attributes, a list of the linked customers, each transaction's
368 content, description, and create time.
370 Accepts the following options:
383 my($self, $session, %param) = @_;
385 $session = $self->session($session);
387 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
388 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
389 return 'Could not load ticket' unless $ticketid;
392 foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
393 my $cust = $link->Target;
394 push @custs, $1 if $cust =~ /\/(\d+)$/;
398 my $transactions = $Ticket->Transactions;
399 while ( my $transaction = $transactions->Next ) {
400 my $t = { created => $transaction->Created,
401 content => $transaction->Content,
402 description => $transaction->Description,
403 type => $transaction->Type,
410 fields => _ticket_info($Ticket),
414 =item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
416 Class method. Retrieve the RT::Ticket object with the specified
417 ticket_id. If custnum is supplied, will also check that the object
418 is a member of that customer. If there is no ticket or the custnum
419 check fails, returns nothing. The meaning of that case is
420 "to this customer, the ticket does not exist".
434 sub get_ticket_object {
436 my ($session, %opt) = @_;
437 $session = $self->session(shift);
438 my $Ticket = RT::Ticket->new($session->{CurrentUser});
439 $Ticket->Load($opt{'ticket_id'});
440 return if ( !$Ticket->id );
441 my $custnum = $opt{'custnum'};
442 if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
443 # probably the most efficient way to check ticket ownership
444 my $Link = RT::Link->new($session->{CurrentUser});
445 $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
447 Target => "freeside://freeside/cust_main/$custnum",
449 return if ( !$Link->id );
455 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
457 Class method. Correspond on a ticket. If there is an error, returns the scalar
458 error. Otherwise, returns the transaction id, error message, and
459 RT::Transaction object.
461 Accepts the following options:
471 Correspondence content
477 sub correspond_ticket {
478 my($self, $session, %param) = @_;
480 $session = $self->session($session);
482 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
483 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
484 return 'Could not load ticket' unless $ticketid;
485 return 'No content' unless $param{'content'};
487 $Ticket->Correspond( Content => $param{'content'} );
490 =item queues SESSION_HASHREF [, ACL ]
492 Retrieve a list of queues. Pass the name of an RT access control right,
493 such as 'CreateTicket', to return only queues on which the current user
494 has that right. Otherwise this will return all queues with the 'SeeQueue'
500 my( $self, $session, $acl ) = @_;
501 $session = $self->session($session);
503 my $showall = $acl ? 0 : 1;
505 my $q = new RT::Queues($session->{'CurrentUser'});
507 while (my $queue = $q->Next) {
508 if ($showall || $queue->CurrentUserHasRight($acl)) {
511 Name => $queue->Name,
512 Description => $queue->Description,
516 return map { $_->{Id} => $_->{Name} } @result;
519 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
520 # to get logged into RT from afar
521 sub _web_external_auth {
522 my( $self, $session ) = @_;
524 my $user = $FS::CurrentUser::CurrentUser->username;
526 eval 'use RT::CurrentUser;';
530 $session->{'CurrentUser'} = RT::CurrentUser->new();
532 warn "$me _web_external_auth loading RT user for $user\n"
535 $session->{'CurrentUser'}->Load($user);
537 if ( ! $session->{'CurrentUser'}->Id() ) {
539 # Create users on-the-fly
541 warn "can't load RT user for $user; auto-creating\n"
544 my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
546 my ( $val, $msg ) = $UserObj->Create(
547 %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
554 # now get user specific information, to better create our user.
556 = RT::Interface::Web::WebExternalAutoInfo($user);
558 # set the attributes that have been defined.
559 # FIXME: this is a horrible kludge. I'm sure there's something cleaner
560 foreach my $attribute (
562 'Signature', 'EmailAddress',
563 'PagerEmailAddress', 'FreeformContactInfo',
564 'Organization', 'Disabled',
565 'Privileged', 'RealName',
567 'EmailEncoding', 'WebEncoding',
568 'ExternalContactInfoId', 'ContactInfoSystem',
569 'ExternalAuthId', 'Gecos',
570 'HomePhone', 'WorkPhone',
571 'MobilePhone', 'PagerPhone',
572 'Address1', 'Address2',
578 #$m->comp( '/Elements/Callback', %ARGS,
579 # _CallbackName => 'NewUser' );
581 my $method = "Set$attribute";
582 $UserObj->$method( $new_user_info->{$attribute} )
583 if ( defined $new_user_info->{$attribute} );
585 $session->{'CurrentUser'}->Load($user);
589 # we failed to successfully create the user. abort abort abort.
590 delete $session->{'CurrentUser'};
592 die "can't auto-create RT user"; #an error message would be nice :/
593 #$m->abort() unless $RT::WebFallbackToInternalAuth;
594 #$m->comp( '/Elements/Login', %ARGS,
595 # Error => loc( 'Cannot create user: [_1]', $msg ) );
599 unless ( $session->{'CurrentUser'}->Id() ) {
600 delete $session->{'CurrentUser'};
602 die "can't auto-create RT user";
605 #if ($RT::WebExternalOnly) {
606 # $m->comp( '/Elements/Login', %ARGS,
607 # Error => loc('You are not an authorized user') );
616 =item selfservice_priority
618 Returns the configured self-service priority field.
622 my $selfservice_priority;
624 sub selfservice_priority {
625 return $selfservice_priority ||= do {
626 my $conf = FS::Conf->new;
627 $conf->config('ticket_system-selfservice_priority_field') || '';