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->{'Current_User'} ) { # 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;
96 eval 'RT::Init("NoSignalHandlers"=>1);';
100 warn "$me init: complete" if $DEBUG;
103 =item customer_tickets CUSTNUM [ LIMIT ] [ PRIORITYVALUE ]
105 Replacement for the one in RT_External so that we can access custom fields
110 # create an RT::Tickets object for a specified custnum or svcnum
112 sub _tickets_search {
113 my ( $self, $type, $number, $limit, $priority ) = @_;
115 $type =~ /^Customer|Service$/ or die "invalid type: $type";
116 $number =~ /^\d+$/ or die "invalid custnum/svcnum: $number";
117 $limit =~ /^\d+$/ or die "invalid limit: $limit";
119 my $session = $self->session();
120 my $CurrentUser = $session->{CurrentUser}
121 or die "unable to create an RT session";
123 my $Tickets = RT::Tickets->new($CurrentUser);
125 # "Customer.number" searches tickets linked via cust_svc also
126 my $rtql = "$type.number = $number";
128 if ( defined( $priority ) ) {
129 my $custom_priority = FS::Conf->new->config('ticket_system-custom_priority_field');
130 if ( length( $priority ) ) {
131 $rtql .= " AND CF.{$custom_priority} = '$priority'";
134 $rtql .= " AND CF.{$custom_priority} IS NULL";
139 join(' OR ', map { "Status = '$_'" } $self->statuses) .
142 warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG;
143 $Tickets->FromSQL($rtql);
145 $Tickets->RowsPerPage($limit);
146 warn "\n\n" . $Tickets->BuildSelectQuery . "\n\n" if $DEBUG > 1;
151 sub href_customer_tickets {
152 my ($self, $custnum) = (shift, shift);
153 if ($custnum =~ /^(\d+)$/) {
154 return $self->href_search_tickets("Customer.number = $custnum", @_);
156 warn "bad custnum $custnum"; '';
159 sub href_service_tickets {
160 my ($self, $svcnum) = (shift, shift);
161 if ($svcnum =~ /^(\d+)$/ ) {
162 return $self->href_search_tickets("Service.number = $svcnum", @_);
164 warn "bad svcnum $svcnum"; '';
167 sub customer_tickets {
169 my $Tickets = $self->_tickets_search('Customer', @_);
171 my $conf = FS::Conf->new;
173 $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
176 { FIELD => 'Priority', ORDER => $priority_order },
177 { FIELD => 'Id', ORDER => 'DESC' },
180 $Tickets->OrderByCols(@order_by);
183 while ( my $t = $Tickets->Next ) {
184 push @tickets, _ticket_info($t);
190 sub num_customer_tickets {
191 my ( $self, $custnum, $priority ) = @_;
192 $self->_tickets_search('Customer', $custnum, 0, $priority)->CountAll;
195 sub service_tickets {
197 my $Tickets = $self->_tickets_search('Service', @_);
199 my $conf = FS::Conf->new;
201 $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
204 { FIELD => 'Priority', ORDER => $priority_order },
205 { FIELD => 'Id', ORDER => 'DESC' },
208 $Tickets->OrderByCols(@order_by);
211 while ( my $t = $Tickets->Next ) {
212 push @tickets, _ticket_info($t);
219 # Takes an RT::Ticket; returns a hashref of the ticket's fields, including
220 # custom fields. Also returns custom and selfservice priority values as
221 # _custom_priority and _selfservice_priority.
224 my $custom_priority =
225 FS::Conf->new->config('ticket_system-custom_priority_field') || '';
226 my $ss_priority = selfservice_priority();
229 foreach my $name ( $t->ReadableAttributes ) {
230 # lowercase names, and skip attributes with non-scalar values
231 $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
233 $ticket_info{'owner'} = $t->OwnerObj->Name;
234 $ticket_info{'queue'} = $t->QueueObj->Name;
235 foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
236 my $name = 'CF.{'.$CF->Name.'}';
237 $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
239 # make this easy to find
240 if ( $custom_priority ) {
241 $ticket_info{'content'} = $ticket_info{"CF.{$custom_priority}"};
243 if ( $ss_priority ) {
244 $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
247 map { $_->Target =~ /cust_svc\/(\d+)/; $1 }
248 @{ $t->Services->ItemsArrayRef }
250 $ticket_info{'svcnums'} = $svcnums;
252 return \%ticket_info;
255 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
257 Class method. Creates a ticket. If there is an error, returns the scalar
258 error, otherwise returns the newly created RT::Ticket object.
260 Accepts the following options:
274 Requestor email address or arrayref of addresses
278 Cc: email address or arrayref of addresses
286 MIME type to use for message. Defaults to text/plain. Specifying text/html
287 can be useful to use HTML markup in message.
291 Customer number (see L<FS::cust_main>) to associate with ticket.
295 Service number (see L<FS::cust_svc>) to associate with ticket. Will also
296 associate the customer who has this service (unless the service is unlinked).
303 my($self, $session, %param) = @_;
305 $session = $self->session($session);
307 my $Queue = RT::Queue->new($session->{'CurrentUser'});
308 $Queue->Load( $param{'queue'} );
310 my $req = ref($param{'requestor'})
311 ? $param{'requestor'}
312 : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
314 my $cc = ref($param{'cc'})
316 : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
318 my $mimeobj = MIME::Entity->build(
319 'Data' => $param{'message'},
320 'Type' => ( $param{'mime_type'} || 'text/plain' ),
324 'Queue' => $Queue->Id,
325 'Subject' => $param{'subject'},
328 'MIMEObj' => $mimeobj,
330 warn Dumper(\%ticket) if $DEBUG > 1;
332 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
333 my( $id, $Transaction, $ErrStr );
336 ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
338 return $ErrStr if $id == 0;
340 warn "ticket got id $id\n" if $DEBUG;
342 #XXX check errors adding custnum/svcnum links (put it in a transaction)...
343 # but we do already know they're good
345 if ( $param{'custnum'} ) {
346 my( $val, $msg ) = $Ticket->_AddLink(
347 'Type' => 'MemberOf',
348 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
352 if ( $param{'svcnum'} ) {
353 my( $val, $msg ) = $Ticket->_AddLink(
354 'Type' => 'MemberOf',
355 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
362 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
364 Class method. Retrieves a ticket. If there is an error, returns the scalar
365 error. Otherwise, currently returns a slightly tricky data structure containing
366 the ticket's attributes, a list of the linked customers, each transaction's
367 content, description, and create time.
369 Accepts the following options:
382 my($self, $session, %param) = @_;
384 $session = $self->session($session);
386 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
387 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
388 return 'Could not load ticket' unless $ticketid;
391 foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
392 my $cust = $link->Target;
393 push @custs, $1 if $cust =~ /\/(\d+)$/;
397 my $transactions = $Ticket->Transactions;
398 while ( my $transaction = $transactions->Next ) {
399 my $t = { created => $transaction->Created,
400 content => $transaction->Content,
401 description => $transaction->Description,
402 type => $transaction->Type,
409 fields => _ticket_info($Ticket),
413 =item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
415 Class method. Retrieve the RT::Ticket object with the specified
416 ticket_id. If custnum is supplied, will also check that the object
417 is a member of that customer. If there is no ticket or the custnum
418 check fails, returns nothing. The meaning of that case is
419 "to this customer, the ticket does not exist".
433 sub get_ticket_object {
435 my ($session, %opt) = @_;
436 $session = $self->session(shift);
437 my $Ticket = RT::Ticket->new($session->{CurrentUser});
438 $Ticket->Load($opt{'ticket_id'});
439 return if ( !$Ticket->id );
440 my $custnum = $opt{'custnum'};
441 if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
442 # probably the most efficient way to check ticket ownership
443 my $Link = RT::Link->new($session->{CurrentUser});
444 $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
446 Target => "freeside://freeside/cust_main/$custnum",
448 return if ( !$Link->id );
454 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
456 Class method. Correspond on a ticket. If there is an error, returns the scalar
457 error. Otherwise, returns the transaction id, error message, and
458 RT::Transaction object.
460 Accepts the following options:
470 Correspondence content
476 sub correspond_ticket {
477 my($self, $session, %param) = @_;
479 $session = $self->session($session);
481 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
482 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
483 return 'Could not load ticket' unless $ticketid;
484 return 'No content' unless $param{'content'};
486 $Ticket->Correspond( Content => $param{'content'} );
489 =item queues SESSION_HASHREF [, ACL ]
491 Retrieve a list of queues. Pass the name of an RT access control right,
492 such as 'CreateTicket', to return only queues on which the current user
493 has that right. Otherwise this will return all queues with the 'SeeQueue'
499 my( $self, $session, $acl ) = @_;
500 $session = $self->session($session);
502 my $showall = $acl ? 0 : 1;
504 my $q = new RT::Queues($session->{'CurrentUser'});
506 while (my $queue = $q->Next) {
507 if ($showall || $queue->CurrentUserHasRight($acl)) {
510 Name => $queue->Name,
511 Description => $queue->Description,
515 return map { $_->{Id} => $_->{Name} } @result;
518 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
519 # to get logged into RT from afar
520 sub _web_external_auth {
521 my( $self, $session ) = @_;
523 my $user = $FS::CurrentUser::CurrentUser->username;
525 eval 'use RT::CurrentUser;';
529 $session->{'CurrentUser'} = RT::CurrentUser->new();
531 warn "$me _web_external_auth loading RT user for $user\n"
534 $session->{'CurrentUser'}->Load($user);
536 if ( ! $session->{'CurrentUser'}->Id() ) {
538 # Create users on-the-fly
540 warn "can't load RT user for $user; auto-creating\n"
543 my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
545 my ( $val, $msg ) = $UserObj->Create(
546 %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
553 # now get user specific information, to better create our user.
555 = RT::Interface::Web::WebExternalAutoInfo($user);
557 # set the attributes that have been defined.
558 # FIXME: this is a horrible kludge. I'm sure there's something cleaner
559 foreach my $attribute (
561 'Signature', 'EmailAddress',
562 'PagerEmailAddress', 'FreeformContactInfo',
563 'Organization', 'Disabled',
564 'Privileged', 'RealName',
566 'EmailEncoding', 'WebEncoding',
567 'ExternalContactInfoId', 'ContactInfoSystem',
568 'ExternalAuthId', 'Gecos',
569 'HomePhone', 'WorkPhone',
570 'MobilePhone', 'PagerPhone',
571 'Address1', 'Address2',
577 #$m->comp( '/Elements/Callback', %ARGS,
578 # _CallbackName => 'NewUser' );
580 my $method = "Set$attribute";
581 $UserObj->$method( $new_user_info->{$attribute} )
582 if ( defined $new_user_info->{$attribute} );
584 $session->{'CurrentUser'}->Load($user);
588 # we failed to successfully create the user. abort abort abort.
589 delete $session->{'CurrentUser'};
591 die "can't auto-create RT user"; #an error message would be nice :/
592 #$m->abort() unless $RT::WebFallbackToInternalAuth;
593 #$m->comp( '/Elements/Login', %ARGS,
594 # Error => loc( 'Cannot create user: [_1]', $msg ) );
598 unless ( $session->{'CurrentUser'}->Id() ) {
599 delete $session->{'CurrentUser'};
601 die "can't auto-create RT user";
604 #if ($RT::WebExternalOnly) {
605 # $m->comp( '/Elements/Login', %ARGS,
606 # Error => loc('You are not an authorized user') );
615 =item selfservice_priority
617 Returns the configured self-service priority field.
621 my $selfservice_priority;
623 sub selfservice_priority {
624 return $selfservice_priority ||= do {
625 my $conf = FS::Conf->new;
626 $conf->config('ticket_system-selfservice_priority_field') || '';