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'} ) {
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 $rtql .= " AND CF.{$custom_priority} = '$priority'";
130 join(' OR ', map { "Status = '$_'" } $self->statuses) .
133 $Tickets->FromSQL($rtql);
135 $Tickets->RowsPerPage($limit);
140 sub customer_tickets {
141 my $Tickets = _customer_tickets_search(@_);
143 my $conf = FS::Conf->new;
145 $conf->exists('ticket_system-priority_reverse') ? 'ASC' : 'DESC';
146 my $custom_priority =
147 $conf->config('ticket_system-custom_priority_field') || '';
150 my $ss_priority = selfservice_priority();
151 push @order_by, { FIELD => "CF.{$ss_priority}", ORDER => $priority_order }
154 { FIELD => 'Priority', ORDER => $priority_order },
155 { FIELD => 'Id', ORDER => 'DESC' },
158 $Tickets->OrderByCols(@order_by);
161 while ( my $t = $Tickets->Next ) {
162 push @tickets, _ticket_info($t);
167 sub num_customer_tickets {
168 my $Tickets = _customer_tickets_search(@_);
169 return $Tickets->CountAll;
173 # Takes an RT::Ticket; returns a hashref of the ticket's fields, including
174 # custom fields. Also returns custom and selfservice priority values as
175 # _custom_priority and _selfservice_priority.
178 my $custom_priority =
179 FS::Conf->new->config('ticket_system-custom_priority_field') || '';
180 my $ss_priority = selfservice_priority();
183 foreach my $name ( $t->ReadableAttributes ) {
184 # lowercase names, and skip attributes with non-scalar values
185 $ticket_info{lc($name)} = $t->$name if !ref($t->$name);
187 $ticket_info{'owner'} = $t->OwnerObj->Name;
188 $ticket_info{'queue'} = $t->QueueObj->Name;
189 foreach my $CF ( @{ $t->CustomFields->ItemsArrayRef } ) {
190 my $name = 'CF.{'.$CF->Name.'}';
191 $ticket_info{$name} = $t->CustomFieldValuesAsString($CF->Id);
193 # make this easy to find
194 if ( $custom_priority ) {
195 $ticket_info{'_custom_priority'} = $ticket_info{"CF.{$custom_priority}"};
197 if ( $ss_priority ) {
198 $ticket_info{'_selfservice_priority'} = $ticket_info{"CF.{$ss_priority}"};
200 return \%ticket_info;
203 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
205 Class method. Creates a ticket. If there is an error, returns the scalar
206 error, otherwise returns the newly created RT::Ticket object.
208 Accepts the following options:
222 Requestor email address or arrayref of addresses
226 Cc: email address or arrayref of addresses
234 MIME type to use for message. Defaults to text/plain. Specifying text/html
235 can be useful to use HTML markup in message.
239 Customer number (see L<FS::cust_main>) to associate with ticket.
243 Service number (see L<FS::cust_svc>) to associate with ticket. Will also
244 associate the customer who has this service (unless the service is unlinked).
251 my($self, $session, %param) = @_;
253 $session = $self->session($session);
255 my $Queue = RT::Queue->new($session->{'CurrentUser'});
256 $Queue->Load( $param{'queue'} );
258 my $req = ref($param{'requestor'})
259 ? $param{'requestor'}
260 : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
262 my $cc = ref($param{'cc'})
264 : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
266 my $mimeobj = MIME::Entity->build(
267 'Data' => $param{'message'},
268 'Type' => ( $param{'mime_type'} || 'text/plain' ),
272 'Queue' => $Queue->Id,
273 'Subject' => $param{'subject'},
276 'MIMEObj' => $mimeobj,
278 warn Dumper(\%ticket) if $DEBUG > 1;
280 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
281 my( $id, $Transaction, $ErrStr );
284 ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
286 return $ErrStr if $id == 0;
288 warn "ticket got id $id\n" if $DEBUG;
290 #XXX check errors adding custnum/svcnum links (put it in a transaction)...
291 # but we do already know they're good
293 if ( $param{'custnum'} ) {
294 my( $val, $msg ) = $Ticket->_AddLink(
295 'Type' => 'MemberOf',
296 'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
300 if ( $param{'svcnum'} ) {
301 my( $val, $msg ) = $Ticket->_AddLink(
302 'Type' => 'MemberOf',
303 'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
310 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
312 Class method. Retrieves a ticket. If there is an error, returns the scalar
313 error. Otherwise, currently returns a slightly tricky data structure containing
314 the ticket's attributes, a list of the linked customers, each transaction's
315 content, description, and create time.
317 Accepts the following options:
330 my($self, $session, %param) = @_;
332 $session = $self->session($session);
334 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
335 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
336 return 'Could not load ticket' unless $ticketid;
339 foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
340 my $cust = $link->Target;
341 push @custs, $1 if $cust =~ /\/(\d+)$/;
345 my $transactions = $Ticket->Transactions;
346 while ( my $transaction = $transactions->Next ) {
347 my $t = { created => $transaction->Created,
348 content => $transaction->Content,
349 description => $transaction->Description,
350 type => $transaction->Type,
357 fields => _ticket_info($Ticket),
361 =item get_ticket_object SESSION_HASHREF, OPTION => VALUE...
363 Class method. Retrieve the RT::Ticket object with the specified
364 ticket_id. If custnum is supplied, will also check that the object
365 is a member of that customer. If there is no ticket or the custnum
366 check fails, returns nothing. The meaning of that case is
367 "to this customer, the ticket does not exist".
381 sub get_ticket_object {
383 my ($session, %opt) = @_;
384 $session = $self->session(shift);
385 my $Ticket = RT::Ticket->new($session->{CurrentUser});
386 $Ticket->Load($opt{'ticket_id'});
387 return if ( !$Ticket->id );
388 my $custnum = $opt{'custnum'};
389 if ( defined($custnum) && $custnum =~ /^\d+$/ ) {
390 # probably the most efficient way to check ticket ownership
391 my $Link = RT::Link->new($session->{CurrentUser});
392 $Link->LoadByCols( LocalBase => $opt{'ticket_id'},
394 Target => "freeside://freeside/cust_main/$custnum",
396 return if ( !$Link->id );
402 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
404 Class method. Correspond on a ticket. If there is an error, returns the scalar
405 error. Otherwise, returns the transaction id, error message, and
406 RT::Transaction object.
408 Accepts the following options:
418 Correspondence content
424 sub correspond_ticket {
425 my($self, $session, %param) = @_;
427 $session = $self->session($session);
429 my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
430 my $ticketid = $Ticket->Load( $param{'ticket_id'} );
431 return 'Could not load ticket' unless $ticketid;
432 return 'No content' unless $param{'content'};
434 $Ticket->Correspond( Content => $param{'content'} );
437 =item queues SESSION_HASHREF [, ACL ]
439 Retrieve a list of queues. Pass the name of an RT access control right,
440 such as 'CreateTicket', to return only queues on which the current user
441 has that right. Otherwise this will return all queues with the 'SeeQueue'
447 my( $self, $session, $acl ) = @_;
448 $session = $self->session($session);
450 my $showall = $acl ? 0 : 1;
452 my $q = new RT::Queues($session->{'CurrentUser'});
454 while (my $queue = $q->Next) {
455 if ($showall || $queue->CurrentUserHasRight($acl)) {
458 Name => $queue->Name,
459 Description => $queue->Description,
463 return map { $_->{Id} => $_->{Name} } @result;
466 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
467 # to get logged into RT from afar
468 sub _web_external_auth {
469 my( $self, $session ) = @_;
471 my $user = $FS::CurrentUser::CurrentUser->username;
473 eval 'use RT::CurrentUser;';
477 $session->{'CurrentUser'} = RT::CurrentUser->new();
479 warn "$me _web_external_auth loading RT user for $user\n"
482 $session->{'CurrentUser'}->Load($user);
484 if ( ! $session->{'CurrentUser'}->Id() ) {
486 # Create users on-the-fly
488 warn "can't load RT user for $user; auto-creating\n"
491 my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
493 my ( $val, $msg ) = $UserObj->Create(
494 %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
501 # now get user specific information, to better create our user.
503 = RT::Interface::Web::WebExternalAutoInfo($user);
505 # set the attributes that have been defined.
506 # FIXME: this is a horrible kludge. I'm sure there's something cleaner
507 foreach my $attribute (
509 'Signature', 'EmailAddress',
510 'PagerEmailAddress', 'FreeformContactInfo',
511 'Organization', 'Disabled',
512 'Privileged', 'RealName',
514 'EmailEncoding', 'WebEncoding',
515 'ExternalContactInfoId', 'ContactInfoSystem',
516 'ExternalAuthId', 'Gecos',
517 'HomePhone', 'WorkPhone',
518 'MobilePhone', 'PagerPhone',
519 'Address1', 'Address2',
525 #$m->comp( '/Elements/Callback', %ARGS,
526 # _CallbackName => 'NewUser' );
528 my $method = "Set$attribute";
529 $UserObj->$method( $new_user_info->{$attribute} )
530 if ( defined $new_user_info->{$attribute} );
532 $session->{'CurrentUser'}->Load($user);
536 # we failed to successfully create the user. abort abort abort.
537 delete $session->{'CurrentUser'};
539 die "can't auto-create RT user"; #an error message would be nice :/
540 #$m->abort() unless $RT::WebFallbackToInternalAuth;
541 #$m->comp( '/Elements/Login', %ARGS,
542 # Error => loc( 'Cannot create user: [_1]', $msg ) );
546 unless ( $session->{'CurrentUser'}->Id() ) {
547 delete $session->{'CurrentUser'};
549 die "can't auto-create RT user";
552 #if ($RT::WebExternalOnly) {
553 # $m->comp( '/Elements/Login', %ARGS,
554 # Error => loc('You are not an authorized user') );
563 =item selfservice_priority
565 Returns the configured self-service priority field.
569 my $selfservice_priority;
571 sub selfservice_priority {
572 return $selfservice_priority ||= do {
573 my $conf = FS::Conf->new;
574 $conf->config('ticket_system-selfservice_priority_field') || '';