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 sub _customer_tickets_search {
111 my ( $self, $custnum, $limit, $priority ) = @_;
113 $custnum =~ /^\d+$/ or die "invalid custnum: $custnum";
114 $limit =~ /^\d+$/ or die "invalid limit: $limit";
116 my $session = $self->session();
117 my $CurrentUser = $session->{CurrentUser}
118 or die "unable to create an RT session";
120 my $Tickets = RT::Tickets->new($CurrentUser);
122 my $rtql = "MemberOf = 'freeside://freeside/cust_main/$custnum'";
124 if ( defined( $priority ) ) {
125 my $custom_priority = FS::Conf->new->config('ticket_system-custom_priority_field');
126 if ( length( $priority ) ) {
127 $rtql .= " AND CF.{$custom_priority} = '$priority'";
130 $rtql .= " AND CF.{$custom_priority} IS NULL";
135 join(' OR ', map { "Status = '$_'" } $self->statuses) .
138 warn "$me _customer_tickets_search:\n$rtql\n" if $DEBUG;
139 $Tickets->FromSQL($rtql);
141 $Tickets->RowsPerPage($limit);
142 warn "\n\n" . $Tickets->BuildSelectQuery . "\n\n" if $DEBUG > 1;
147 sub customer_tickets {
148 my $Tickets = _customer_tickets_search(@_);
150 my $conf = FS::Conf->new;
152 $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
155 { FIELD => 'Priority', ORDER => $priority_order },
156 { FIELD => 'Id', ORDER => 'DESC' },
159 $Tickets->OrderByCols(@order_by);
162 while ( my $t = $Tickets->Next ) {
163 push @tickets, _ticket_info($t);
169 sub num_customer_tickets {
170 my ( $self, $custnum, $priority ) = @_;
171 my $Tickets = $self->_customer_tickets_search($custnum, 0, $priority);
172 return $Tickets->CountAll;
176 # Takes an RT::Ticket; returns a hashref of the ticket's fields, including
177 # custom fields. Also returns custom and selfservice priority values as
178 # _custom_priority and _selfservice_priority.
181 my $custom_priority =
182 FS::Conf->new->config('ticket_system-custom_priority_field') || '';
183 my $ss_priority = selfservice_priority();
186 foreach my $name ( $t->ReadableAttributes ) {
187 # lowercase names, and skip attributes with non-scalar values
188 $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
190 $ticket_info{'owner'} = $t->OwnerObj->Name;
191 $ticket_info{'queue'} = $t->QueueObj->Name;
192 foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
193 my $name = 'CF.{'.$CF->Name.'}';
194 $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
196 # make this easy to find
197 if ( $custom_priority ) {
198 $ticket_info{'content'} = $ticket_info{"CF.{$custom_priority}"};
200 if ( $ss_priority ) {
201 $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
203 return \%ticket_info;
206 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
208 Class method. Creates a ticket. If there is an error, returns the scalar
209 error, otherwise returns the newly created RT::Ticket object.
211 Accepts the following options:
225 Requestor email address or arrayref of addresses
229 Cc: email address or arrayref of addresses
237 MIME type to use for message. Defaults to text/plain. Specifying text/html
238 can be useful to use HTML markup in message.
242 Customer number (see L<FS::cust_main>) to associate with ticket.
246 Service number (see L<FS::cust_svc>) to associate with ticket. Will also
247 associate the customer who has this service (unless the service is unlinked).
254 my($self, $session, %param) = @_;
256 $session = $self->session($session);
258 my $Queue = RT::Queue->new($session->{'CurrentUser'});
259 $Queue->Load( $param{'queue'} );
261 my $req = ref($param{'requestor'})
262 ? $param{'requestor'}
263 : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
265 my $cc = ref($param{'cc'})
267 : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
269 my $mimeobj = MIME::Entity->build(
270 'Data' => $param{'message'},
271 'Type' => ( $param{'mime_type'} || 'text/plain' ),
275 'Queue' => $Queue->Id,
276 'Subject' => $param{'subject'},
279 'MIMEObj' => $mimeobj,
281 warn Dumper(\%ticket) if $DEBUG > 1;
283 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
284 my( $id, $Transaction, $ErrStr );
287 ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
289 return $ErrStr if $id == 0;
291 warn "ticket got id $id\n" if $DEBUG;
293 #XXX check errors adding custnum/svcnum links (put it in a transaction)...
294 # but we do already know they're good
296 if ( $param{'custnum'} ) {
297 my( $val, $msg ) = $Ticket->_AddLink(
298 'Type' => 'MemberOf',
299 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
303 if ( $param{'svcnum'} ) {
304 my( $val, $msg ) = $Ticket->_AddLink(
305 'Type' => 'MemberOf',
306 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
313 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
315 Class method. Retrieves a ticket. If there is an error, returns the scalar
316 error. Otherwise, currently returns a slightly tricky data structure containing
317 the ticket's attributes, a list of the linked customers, each transaction's
318 content, description, and create time.
320 Accepts the following options:
333 my($self, $session, %param) = @_;
335 $session = $self->session($session);
337 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
338 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
339 return 'Could not load ticket' unless $ticketid;
342 foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
343 my $cust = $link->Target;
344 push @custs, $1 if $cust =~ /\/(\d+)$/;
348 my $transactions = $Ticket->Transactions;
349 while ( my $transaction = $transactions->Next ) {
350 my $t = { created => $transaction->Created,
351 content => $transaction->Content,
352 description => $transaction->Description,
353 type => $transaction->Type,
360 fields => _ticket_info($Ticket),
364 =item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
366 Class method. Retrieve the RT::Ticket object with the specified
367 ticket_id. If custnum is supplied, will also check that the object
368 is a member of that customer. If there is no ticket or the custnum
369 check fails, returns nothing. The meaning of that case is
370 "to this customer, the ticket does not exist".
384 sub get_ticket_object {
386 my ($session, %opt) = @_;
387 $session = $self->session(shift);
388 my $Ticket = RT::Ticket->new($session->{CurrentUser});
389 $Ticket->Load($opt{'ticket_id'});
390 return if ( !$Ticket->id );
391 my $custnum = $opt{'custnum'};
392 if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
393 # probably the most efficient way to check ticket ownership
394 my $Link = RT::Link->new($session->{CurrentUser});
395 $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
397 Target => "freeside://freeside/cust_main/$custnum",
399 return if ( !$Link->id );
405 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
407 Class method. Correspond on a ticket. If there is an error, returns the scalar
408 error. Otherwise, returns the transaction id, error message, and
409 RT::Transaction object.
411 Accepts the following options:
421 Correspondence content
427 sub correspond_ticket {
428 my($self, $session, %param) = @_;
430 $session = $self->session($session);
432 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
433 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
434 return 'Could not load ticket' unless $ticketid;
435 return 'No content' unless $param{'content'};
437 $Ticket->Correspond( Content => $param{'content'} );
440 =item queues SESSION_HASHREF [, ACL ]
442 Retrieve a list of queues. Pass the name of an RT access control right,
443 such as 'CreateTicket', to return only queues on which the current user
444 has that right. Otherwise this will return all queues with the 'SeeQueue'
450 my( $self, $session, $acl ) = @_;
451 $session = $self->session($session);
453 my $showall = $acl ? 0 : 1;
455 my $q = new RT::Queues($session->{'CurrentUser'});
457 while (my $queue = $q->Next) {
458 if ($showall || $queue->CurrentUserHasRight($acl)) {
461 Name => $queue->Name,
462 Description => $queue->Description,
466 return map { $_->{Id} => $_->{Name} } @result;
469 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
470 # to get logged into RT from afar
471 sub _web_external_auth {
472 my( $self, $session ) = @_;
474 my $user = $FS::CurrentUser::CurrentUser->username;
476 eval 'use RT::CurrentUser;';
480 $session->{'CurrentUser'} = RT::CurrentUser->new();
482 warn "$me _web_external_auth loading RT user for $user\n"
485 $session->{'CurrentUser'}->Load($user);
487 if ( ! $session->{'CurrentUser'}->Id() ) {
489 # Create users on-the-fly
491 warn "can't load RT user for $user; auto-creating\n"
494 my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
496 my ( $val, $msg ) = $UserObj->Create(
497 %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
504 # now get user specific information, to better create our user.
506 = RT::Interface::Web::WebExternalAutoInfo($user);
508 # set the attributes that have been defined.
509 # FIXME: this is a horrible kludge. I'm sure there's something cleaner
510 foreach my $attribute (
512 'Signature', 'EmailAddress',
513 'PagerEmailAddress', 'FreeformContactInfo',
514 'Organization', 'Disabled',
515 'Privileged', 'RealName',
517 'EmailEncoding', 'WebEncoding',
518 'ExternalContactInfoId', 'ContactInfoSystem',
519 'ExternalAuthId', 'Gecos',
520 'HomePhone', 'WorkPhone',
521 'MobilePhone', 'PagerPhone',
522 'Address1', 'Address2',
528 #$m->comp( '/Elements/Callback', %ARGS,
529 # _CallbackName => 'NewUser' );
531 my $method = "Set$attribute";
532 $UserObj->$method( $new_user_info->{$attribute} )
533 if ( defined $new_user_info->{$attribute} );
535 $session->{'CurrentUser'}->Load($user);
539 # we failed to successfully create the user. abort abort abort.
540 delete $session->{'CurrentUser'};
542 die "can't auto-create RT user"; #an error message would be nice :/
543 #$m->abort() unless $RT::WebFallbackToInternalAuth;
544 #$m->comp( '/Elements/Login', %ARGS,
545 # Error => loc( 'Cannot create user: [_1]', $msg ) );
549 unless ( $session->{'CurrentUser'}->Id() ) {
550 delete $session->{'CurrentUser'};
552 die "can't auto-create RT user";
555 #if ($RT::WebExternalOnly) {
556 # $m->comp( '/Elements/Login', %ARGS,
557 # Error => loc('You are not an authorized user') );
566 =item selfservice_priority
568 Returns the configured self-service priority field.
572 my $selfservice_priority;
574 sub selfservice_priority {
575 return $selfservice_priority ||= do {
576 my $conf = FS::Conf->new;
577 $conf->config('ticket_system-selfservice_priority_field') || '';