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';
153 my $custom_priority =
154 $conf->config('ticket_system-custom_priority_field') || '';
157 my $ss_priority = selfservice_priority();
158 push @order_by, { FIELD => "CF.{$ss_priority}", ORDER => $priority_order }
161 { FIELD => 'Priority', ORDER => $priority_order },
162 { FIELD => 'Id', ORDER => 'DESC' },
165 $Tickets->OrderByCols(@order_by);
168 while ( my $t = $Tickets->Next ) {
169 push @tickets, _ticket_info($t);
174 sub num_customer_tickets {
175 my ( $self, $custnum, $priority ) = @_;
176 my $Tickets = $self->_customer_tickets_search($custnum, 0, $priority);
177 return $Tickets->CountAll;
181 # Takes an RT::Ticket; returns a hashref of the ticket's fields, including
182 # custom fields. Also returns custom and selfservice priority values as
183 # _custom_priority and _selfservice_priority.
186 my $custom_priority =
187 FS::Conf->new->config('ticket_system-custom_priority_field') || '';
188 my $ss_priority = selfservice_priority();
191 foreach my $name ( $t->ReadableAttributes ) {
192 # lowercase names, and skip attributes with non-scalar values
193 $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
195 $ticket_info{'owner'} = $t->OwnerObj->Name;
196 $ticket_info{'queue'} = $t->QueueObj->Name;
197 foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
198 my $name = 'CF.{'.$CF->Name.'}';
199 $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
201 # make this easy to find
202 if ( $custom_priority ) {
203 $ticket_info{'content'} = $ticket_info{"CF.{$custom_priority}"};
205 if ( $ss_priority ) {
206 $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
208 return \%ticket_info;
211 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
213 Class method. Creates a ticket. If there is an error, returns the scalar
214 error, otherwise returns the newly created RT::Ticket object.
216 Accepts the following options:
230 Requestor email address or arrayref of addresses
234 Cc: email address or arrayref of addresses
242 MIME type to use for message. Defaults to text/plain. Specifying text/html
243 can be useful to use HTML markup in message.
247 Customer number (see L<FS::cust_main>) to associate with ticket.
251 Service number (see L<FS::cust_svc>) to associate with ticket. Will also
252 associate the customer who has this service (unless the service is unlinked).
259 my($self, $session, %param) = @_;
261 $session = $self->session($session);
263 my $Queue = RT::Queue->new($session->{'CurrentUser'});
264 $Queue->Load( $param{'queue'} );
266 my $req = ref($param{'requestor'})
267 ? $param{'requestor'}
268 : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
270 my $cc = ref($param{'cc'})
272 : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
274 my $mimeobj = MIME::Entity->build(
275 'Data' => $param{'message'},
276 'Type' => ( $param{'mime_type'} || 'text/plain' ),
280 'Queue' => $Queue->Id,
281 'Subject' => $param{'subject'},
284 'MIMEObj' => $mimeobj,
286 warn Dumper(\%ticket) if $DEBUG > 1;
288 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
289 my( $id, $Transaction, $ErrStr );
292 ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
294 return $ErrStr if $id == 0;
296 warn "ticket got id $id\n" if $DEBUG;
298 #XXX check errors adding custnum/svcnum links (put it in a transaction)...
299 # but we do already know they're good
301 if ( $param{'custnum'} ) {
302 my( $val, $msg ) = $Ticket->_AddLink(
303 'Type' => 'MemberOf',
304 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
308 if ( $param{'svcnum'} ) {
309 my( $val, $msg ) = $Ticket->_AddLink(
310 'Type' => 'MemberOf',
311 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
318 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
320 Class method. Retrieves a ticket. If there is an error, returns the scalar
321 error. Otherwise, currently returns a slightly tricky data structure containing
322 the ticket's attributes, a list of the linked customers, each transaction's
323 content, description, and create time.
325 Accepts the following options:
338 my($self, $session, %param) = @_;
340 $session = $self->session($session);
342 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
343 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
344 return 'Could not load ticket' unless $ticketid;
347 foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
348 my $cust = $link->Target;
349 push @custs, $1 if $cust =~ /\/(\d+)$/;
353 my $transactions = $Ticket->Transactions;
354 while ( my $transaction = $transactions->Next ) {
355 my $t = { created => $transaction->Created,
356 content => $transaction->Content,
357 description => $transaction->Description,
358 type => $transaction->Type,
365 fields => _ticket_info($Ticket),
369 =item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
371 Class method. Retrieve the RT::Ticket object with the specified
372 ticket_id. If custnum is supplied, will also check that the object
373 is a member of that customer. If there is no ticket or the custnum
374 check fails, returns nothing. The meaning of that case is
375 "to this customer, the ticket does not exist".
389 sub get_ticket_object {
391 my ($session, %opt) = @_;
392 $session = $self->session(shift);
393 my $Ticket = RT::Ticket->new($session->{CurrentUser});
394 $Ticket->Load($opt{'ticket_id'});
395 return if ( !$Ticket->id );
396 my $custnum = $opt{'custnum'};
397 if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
398 # probably the most efficient way to check ticket ownership
399 my $Link = RT::Link->new($session->{CurrentUser});
400 $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
402 Target => "freeside://freeside/cust_main/$custnum",
404 return if ( !$Link->id );
410 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
412 Class method. Correspond on a ticket. If there is an error, returns the scalar
413 error. Otherwise, returns the transaction id, error message, and
414 RT::Transaction object.
416 Accepts the following options:
426 Correspondence content
432 sub correspond_ticket {
433 my($self, $session, %param) = @_;
435 $session = $self->session($session);
437 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
438 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
439 return 'Could not load ticket' unless $ticketid;
440 return 'No content' unless $param{'content'};
442 $Ticket->Correspond( Content => $param{'content'} );
445 =item queues SESSION_HASHREF [, ACL ]
447 Retrieve a list of queues. Pass the name of an RT access control right,
448 such as 'CreateTicket', to return only queues on which the current user
449 has that right. Otherwise this will return all queues with the 'SeeQueue'
455 my( $self, $session, $acl ) = @_;
456 $session = $self->session($session);
458 my $showall = $acl ? 0 : 1;
460 my $q = new RT::Queues($session->{'CurrentUser'});
462 while (my $queue = $q->Next) {
463 if ($showall || $queue->CurrentUserHasRight($acl)) {
466 Name => $queue->Name,
467 Description => $queue->Description,
471 return map { $_->{Id} => $_->{Name} } @result;
474 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
475 # to get logged into RT from afar
476 sub _web_external_auth {
477 my( $self, $session ) = @_;
479 my $user = $FS::CurrentUser::CurrentUser->username;
481 eval 'use RT::CurrentUser;';
485 $session->{'CurrentUser'} = RT::CurrentUser->new();
487 warn "$me _web_external_auth loading RT user for $user\n"
490 $session->{'CurrentUser'}->Load($user);
492 if ( ! $session->{'CurrentUser'}->Id() ) {
494 # Create users on-the-fly
496 warn "can't load RT user for $user; auto-creating\n"
499 my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
501 my ( $val, $msg ) = $UserObj->Create(
502 %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
509 # now get user specific information, to better create our user.
511 = RT::Interface::Web::WebExternalAutoInfo($user);
513 # set the attributes that have been defined.
514 # FIXME: this is a horrible kludge. I'm sure there's something cleaner
515 foreach my $attribute (
517 'Signature', 'EmailAddress',
518 'PagerEmailAddress', 'FreeformContactInfo',
519 'Organization', 'Disabled',
520 'Privileged', 'RealName',
522 'EmailEncoding', 'WebEncoding',
523 'ExternalContactInfoId', 'ContactInfoSystem',
524 'ExternalAuthId', 'Gecos',
525 'HomePhone', 'WorkPhone',
526 'MobilePhone', 'PagerPhone',
527 'Address1', 'Address2',
533 #$m->comp( '/Elements/Callback', %ARGS,
534 # _CallbackName => 'NewUser' );
536 my $method = "Set$attribute";
537 $UserObj->$method( $new_user_info->{$attribute} )
538 if ( defined $new_user_info->{$attribute} );
540 $session->{'CurrentUser'}->Load($user);
544 # we failed to successfully create the user. abort abort abort.
545 delete $session->{'CurrentUser'};
547 die "can't auto-create RT user"; #an error message would be nice :/
548 #$m->abort() unless $RT::WebFallbackToInternalAuth;
549 #$m->comp( '/Elements/Login', %ARGS,
550 # Error => loc( 'Cannot create user: [_1]', $msg ) );
554 unless ( $session->{'CurrentUser'}->Id() ) {
555 delete $session->{'CurrentUser'};
557 die "can't auto-create RT user";
560 #if ($RT::WebExternalOnly) {
561 # $m->comp( '/Elements/Login', %ARGS,
562 # Error => loc('You are not an authorized user') );
571 =item selfservice_priority
573 Returns the configured self-service priority field.
577 my $selfservice_priority;
579 sub selfservice_priority {
580 return $selfservice_priority ||= do {
581 my $conf = FS::Conf->new;
582 $conf->config('ticket_system-selfservice_priority_field') || '';