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 $Tickets = _customer_tickets_search(@_);
176 return $Tickets->CountAll;
180 # Takes an RT::Ticket; returns a hashref of the ticket's fields, including
181 # custom fields. Also returns custom and selfservice priority values as
182 # _custom_priority and _selfservice_priority.
185 my $custom_priority =
186 FS::Conf->new->config('ticket_system-custom_priority_field') || '';
187 my $ss_priority = selfservice_priority();
190 foreach my $name ( $t->ReadableAttributes ) {
191 # lowercase names, and skip attributes with non-scalar values
192 $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
194 $ticket_info{'owner'} = $t->OwnerObj->Name;
195 $ticket_info{'queue'} = $t->QueueObj->Name;
196 foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
197 my $name = 'CF.{'.$CF->Name.'}';
198 $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
200 # make this easy to find
201 if ( $custom_priority ) {
202 $ticket_info{'content'} = $ticket_info{"CF.{$custom_priority}"};
204 if ( $ss_priority ) {
205 $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
207 return \%ticket_info;
210 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
212 Class method. Creates a ticket. If there is an error, returns the scalar
213 error, otherwise returns the newly created RT::Ticket object.
215 Accepts the following options:
229 Requestor email address or arrayref of addresses
233 Cc: email address or arrayref of addresses
241 MIME type to use for message. Defaults to text/plain. Specifying text/html
242 can be useful to use HTML markup in message.
246 Customer number (see L<FS::cust_main>) to associate with ticket.
250 Service number (see L<FS::cust_svc>) to associate with ticket. Will also
251 associate the customer who has this service (unless the service is unlinked).
258 my($self, $session, %param) = @_;
260 $session = $self->session($session);
262 my $Queue = RT::Queue->new($session->{'CurrentUser'});
263 $Queue->Load( $param{'queue'} );
265 my $req = ref($param{'requestor'})
266 ? $param{'requestor'}
267 : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
269 my $cc = ref($param{'cc'})
271 : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
273 my $mimeobj = MIME::Entity->build(
274 'Data' => $param{'message'},
275 'Type' => ( $param{'mime_type'} || 'text/plain' ),
279 'Queue' => $Queue->Id,
280 'Subject' => $param{'subject'},
283 'MIMEObj' => $mimeobj,
285 warn Dumper(\%ticket) if $DEBUG > 1;
287 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
288 my( $id, $Transaction, $ErrStr );
291 ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
293 return $ErrStr if $id == 0;
295 warn "ticket got id $id\n" if $DEBUG;
297 #XXX check errors adding custnum/svcnum links (put it in a transaction)...
298 # but we do already know they're good
300 if ( $param{'custnum'} ) {
301 my( $val, $msg ) = $Ticket->_AddLink(
302 'Type' => 'MemberOf',
303 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
307 if ( $param{'svcnum'} ) {
308 my( $val, $msg ) = $Ticket->_AddLink(
309 'Type' => 'MemberOf',
310 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
317 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
319 Class method. Retrieves a ticket. If there is an error, returns the scalar
320 error. Otherwise, currently returns a slightly tricky data structure containing
321 the ticket's attributes, a list of the linked customers, each transaction's
322 content, description, and create time.
324 Accepts the following options:
337 my($self, $session, %param) = @_;
339 $session = $self->session($session);
341 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
342 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
343 return 'Could not load ticket' unless $ticketid;
346 foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
347 my $cust = $link->Target;
348 push @custs, $1 if $cust =~ /\/(\d+)$/;
352 my $transactions = $Ticket->Transactions;
353 while ( my $transaction = $transactions->Next ) {
354 my $t = { created => $transaction->Created,
355 content => $transaction->Content,
356 description => $transaction->Description,
357 type => $transaction->Type,
364 fields => _ticket_info($Ticket),
368 =item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
370 Class method. Retrieve the RT::Ticket object with the specified
371 ticket_id. If custnum is supplied, will also check that the object
372 is a member of that customer. If there is no ticket or the custnum
373 check fails, returns nothing. The meaning of that case is
374 "to this customer, the ticket does not exist".
388 sub get_ticket_object {
390 my ($session, %opt) = @_;
391 $session = $self->session(shift);
392 my $Ticket = RT::Ticket->new($session->{CurrentUser});
393 $Ticket->Load($opt{'ticket_id'});
394 return if ( !$Ticket->id );
395 my $custnum = $opt{'custnum'};
396 if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
397 # probably the most efficient way to check ticket ownership
398 my $Link = RT::Link->new($session->{CurrentUser});
399 $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
401 Target => "freeside://freeside/cust_main/$custnum",
403 return if ( !$Link->id );
409 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
411 Class method. Correspond on a ticket. If there is an error, returns the scalar
412 error. Otherwise, returns the transaction id, error message, and
413 RT::Transaction object.
415 Accepts the following options:
425 Correspondence content
431 sub correspond_ticket {
432 my($self, $session, %param) = @_;
434 $session = $self->session($session);
436 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
437 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
438 return 'Could not load ticket' unless $ticketid;
439 return 'No content' unless $param{'content'};
441 $Ticket->Correspond( Content => $param{'content'} );
444 =item queues SESSION_HASHREF [, ACL ]
446 Retrieve a list of queues. Pass the name of an RT access control right,
447 such as 'CreateTicket', to return only queues on which the current user
448 has that right. Otherwise this will return all queues with the 'SeeQueue'
454 my( $self, $session, $acl ) = @_;
455 $session = $self->session($session);
457 my $showall = $acl ? 0 : 1;
459 my $q = new RT::Queues($session->{'CurrentUser'});
461 while (my $queue = $q->Next) {
462 if ($showall || $queue->CurrentUserHasRight($acl)) {
465 Name => $queue->Name,
466 Description => $queue->Description,
470 return map { $_->{Id} => $_->{Name} } @result;
473 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
474 # to get logged into RT from afar
475 sub _web_external_auth {
476 my( $self, $session ) = @_;
478 my $user = $FS::CurrentUser::CurrentUser->username;
480 eval 'use RT::CurrentUser;';
484 $session->{'CurrentUser'} = RT::CurrentUser->new();
486 warn "$me _web_external_auth loading RT user for $user\n"
489 $session->{'CurrentUser'}->Load($user);
491 if ( ! $session->{'CurrentUser'}->Id() ) {
493 # Create users on-the-fly
495 warn "can't load RT user for $user; auto-creating\n"
498 my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
500 my ( $val, $msg ) = $UserObj->Create(
501 %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
508 # now get user specific information, to better create our user.
510 = RT::Interface::Web::WebExternalAutoInfo($user);
512 # set the attributes that have been defined.
513 # FIXME: this is a horrible kludge. I'm sure there's something cleaner
514 foreach my $attribute (
516 'Signature', 'EmailAddress',
517 'PagerEmailAddress', 'FreeformContactInfo',
518 'Organization', 'Disabled',
519 'Privileged', 'RealName',
521 'EmailEncoding', 'WebEncoding',
522 'ExternalContactInfoId', 'ContactInfoSystem',
523 'ExternalAuthId', 'Gecos',
524 'HomePhone', 'WorkPhone',
525 'MobilePhone', 'PagerPhone',
526 'Address1', 'Address2',
532 #$m->comp( '/Elements/Callback', %ARGS,
533 # _CallbackName => 'NewUser' );
535 my $method = "Set$attribute";
536 $UserObj->$method( $new_user_info->{$attribute} )
537 if ( defined $new_user_info->{$attribute} );
539 $session->{'CurrentUser'}->Load($user);
543 # we failed to successfully create the user. abort abort abort.
544 delete $session->{'CurrentUser'};
546 die "can't auto-create RT user"; #an error message would be nice :/
547 #$m->abort() unless $RT::WebFallbackToInternalAuth;
548 #$m->comp( '/Elements/Login', %ARGS,
549 # Error => loc( 'Cannot create user: [_1]', $msg ) );
553 unless ( $session->{'CurrentUser'}->Id() ) {
554 delete $session->{'CurrentUser'};
556 die "can't auto-create RT user";
559 #if ($RT::WebExternalOnly) {
560 # $m->comp( '/Elements/Login', %ARGS,
561 # Error => loc('You are not an authorized user') );
570 =item selfservice_priority
572 Returns the configured self-service priority field.
576 my $selfservice_priority;
578 sub selfservice_priority {
579 return $selfservice_priority ||= do {
580 my $conf = FS::Conf->new;
581 $conf->config('ticket_system-selfservice_priority_field') || '';