self-service improvements, RT10883
[freeside.git] / FS / FS / TicketSystem / RT_Internal.pm
1 package FS::TicketSystem::RT_Internal;
2
3 use strict;
4 use vars qw( @ISA $DEBUG $me );
5 use Data::Dumper;
6 use MIME::Entity;
7 use FS::UID qw(dbh);
8 use FS::CGI qw(popurl);
9 use FS::TicketSystem::RT_Libs;
10
11 @ISA = qw( FS::TicketSystem::RT_Libs );
12
13 $DEBUG = 0;
14 $me = '[FS::TicketSystem::RT_Internal]';
15
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
21    )";
22 }
23
24 sub baseurl {
25   #my $self = shift;
26   if ( $RT::URI::freeside::URL ) {
27     $RT::URI::freeside::URL. '/rt/';
28   } else {
29     'http://you_need_to_set_RT_URI_freeside_URL_in_SiteConfig.pm/';
30   }
31 }
32
33 #mapping/genericize??
34 #ShowConfigTab ModifySelf
35 sub access_right {
36   my( $self, $session, $right ) = @_;
37
38   #return '' unless $conf->config('ticket_system');
39   return '' unless FS::Conf->new->config('ticket_system');
40
41   $session = $self->session($session);
42
43   #warn "$me access_right: CurrentUser ". $session->{'CurrentUser'}. ":\n".
44   #     ( $DEBUG>1 ? Dumper($session->{'CurrentUser'}) : '' )
45   #  if $DEBUG > 1;
46
47   $session->{'CurrentUser'}->HasRight( Right  => $right,
48                                        Object => $RT::System );
49 }
50
51 sub session {
52   my( $self, $session ) = @_;
53
54   if ( $session && $session->{'Current_User'} ) {
55     warn "$me session: using existing session and CurrentUser: \n".
56          Dumper($session->{'CurrentUser'})
57       if $DEBUG;
58  } else {
59     warn "$me session: loading session and CurrentUser\n" if $DEBUG > 1;
60     $session = $self->_web_external_auth($session);
61   }
62
63   $session;
64 }
65
66 sub init {
67   my $self = shift;
68
69   warn "$me init: loading RT libraries\n" if $DEBUG;
70   eval '
71     use lib ( "/opt/rt3/local/lib", "/opt/rt3/lib" );
72     use RT;
73     #it looks like the rest are taken care of these days in RT::InitClasses
74     #use RT::Ticket;
75     #use RT::Transactions;
76     #use RT::Users;
77     #use RT::CurrentUser;
78     #use RT::Templates;
79     #use RT::Queues;
80     #use RT::ScripActions;
81     #use RT::ScripConditions;
82     #use RT::Scrips;
83     #use RT::Groups;
84     #use RT::GroupMembers;
85     #use RT::CustomFields;
86     #use RT::CustomFieldValues;
87     #use RT::ObjectCustomFieldValues;
88
89     #for web external auth...
90     use RT::Interface::Web;
91   ';
92   die $@ if $@;
93
94   warn "$me init: loading RT config\n" if $DEBUG;
95   {
96     local $SIG{__DIE__};
97     eval 'RT::LoadConfig();';
98   }
99   die $@ if $@;
100
101   warn "$me init: initializing RT\n" if $DEBUG;
102   {
103     local $SIG{__DIE__};
104     eval 'RT::Init("NoSignalHandlers"=>1);';
105   }
106   die $@ if $@;
107
108   warn "$me init: complete" if $DEBUG;
109 }
110
111 =item create_ticket SESSION_HASHREF, OPTION => VALUE ...
112
113 Class method.  Creates a ticket.  If there is an error, returns the scalar
114 error, otherwise returns the newly created RT::Ticket object.
115
116 Accepts the following options:
117
118 =over 4
119
120 =item queue
121
122 Queue name or Id
123
124 =item subject
125
126 Ticket subject
127
128 =item requestor
129
130 Requestor email address or arrayref of addresses
131
132 =item cc
133
134 Cc: email address or arrayref of addresses
135
136 =item message
137
138 Ticket message
139
140 =item mime_type
141
142 MIME type to use for message.  Defaults to text/plain.  Specifying text/html
143 can be useful to use HTML markup in message.
144
145 =item custnum
146
147 Customer number (see L<FS::cust_main>) to associate with ticket.
148
149 =item svcnum
150
151 Service number (see L<FS::cust_svc>) to associate with ticket.  Will also
152 associate the customer who has this service (unless the service is unlinked).
153
154 =back
155
156 =cut
157
158 sub create_ticket {
159   my($self, $session, %param) = @_;
160
161   $session = $self->session($session);
162
163   my $Queue = RT::Queue->new($session->{'CurrentUser'});
164   $Queue->Load( $param{'queue'} );
165
166   my $req = ref($param{'requestor'})
167               ? $param{'requestor'}
168               : ( $param{'requestor'} ? [ $param{'requestor'} ] : [] );
169
170   my $cc = ref($param{'cc'})
171              ? $param{'cc'}
172              : ( $param{'cc'} ? [ $param{'cc'} ] : [] );
173
174   my $mimeobj = MIME::Entity->build(
175     'Data' => $param{'message'},
176     'Type' => ( $param{'mime_type'} || 'text/plain' ),
177   );
178
179   my %ticket = (
180     'Queue'     => $Queue->Id,
181     'Subject'   => $param{'subject'},
182     'Requestor' => $req,
183     'Cc'        => $cc,
184     'MIMEObj'   => $mimeobj,
185   );
186   warn Dumper(\%ticket) if $DEBUG > 1;
187
188   my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
189   my( $id, $Transaction, $ErrStr );
190   {
191     local $SIG{__DIE__};
192     ( $id, $Transaction, $ErrStr ) = $Ticket->Create( %ticket );
193   }
194   return $ErrStr if $id == 0;
195
196   warn "ticket got id $id\n" if $DEBUG;
197
198   #XXX check errors adding custnum/svcnum links (put it in a transaction)...
199   # but we do already know they're good
200
201   if ( $param{'custnum'} ) {
202     my( $val, $msg ) = $Ticket->_AddLink(
203      'Type'   => 'MemberOf',
204      'Target' => 'freeside://freeside/cust_main/'. $param{'custnum'},
205     );
206   }
207
208   if ( $param{'svcnum'} ) {
209     my( $val, $msg ) = $Ticket->_AddLink(
210      'Type'   => 'MemberOf',
211      'Target' => 'freeside://freeside/cust_svc/'. $param{'svcnum'},
212     );
213   }
214
215   $Ticket;
216 }
217
218 =item get_ticket SESSION_HASHREF, OPTION => VALUE ...
219
220 Class method. Retrieves a ticket. If there is an error, returns the scalar
221 error. Otherwise, currently returns a slightly tricky data structure containing
222 a list of the linked customers and each transaction's content, description, and
223 create time.
224
225 Accepts the following options:
226
227 =over 4
228
229 =item ticket_id 
230
231 The ticket id
232
233 =back
234
235 =cut
236
237 sub get_ticket {
238   my($self, $session, %param) = @_;
239
240   $session = $self->session($session);
241
242   my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
243   my $ticketid = $Ticket->Load( $param{'ticket_id'} );
244   return 'Could not load ticket' unless $ticketid;
245
246   my @custs = ();
247   foreach my $link ( @{ $Ticket->Customers->ItemsArrayRef } ) {
248     my $cust = $link->Target;
249     push @custs, $1 if $cust =~ /\/(\d+)$/;
250   }
251
252   my @txns = ();
253   my $transactions = $Ticket->Transactions;
254   while ( my $transaction = $transactions->Next ) {
255     my $t = { created => $transaction->Created,
256         content => $transaction->Content,
257         description => $transaction->Description,
258     };
259     push @txns, $t;
260   }
261
262   { txns => [ @txns ],
263     custs => [ @custs ],
264   };
265 }
266
267
268 =item correspond_ticket SESSION_HASHREF, OPTION => VALUE ...
269
270 Class method. Correspond on a ticket. If there is an error, returns the scalar
271 error. Otherwise, returns the transaction id, error message, and
272 RT::Transaction object.
273
274 Accepts the following options:
275
276 =over 4
277
278 =item ticket_id 
279
280 The ticket id
281
282 =item content
283
284 Correspondence content
285
286 =back
287
288 =cut
289
290 sub correspond_ticket {
291   my($self, $session, %param) = @_;
292
293   $session = $self->session($session);
294
295   my $Ticket = RT::Ticket->new($session->{'CurrentUser'});
296   my $ticketid = $Ticket->Load( $param{'ticket_id'} );
297   return 'Could not load ticket' unless $ticketid;
298   return 'No content' unless $param{'content'};
299
300   $Ticket->Correspond( Content => $param{'content'} );
301 }
302
303 #shameless false laziness w/RT::Interface::Web::AttemptExternalAuth
304 # to get logged into RT from afar
305 sub _web_external_auth {
306   my( $self, $session ) = @_;
307
308   my $user = $FS::CurrentUser::CurrentUser->username;
309
310   eval 'use RT::CurrentUser;';
311   die $@ if $@;
312
313   $session ||= {};
314   $session->{'CurrentUser'} = RT::CurrentUser->new();
315
316   warn "$me _web_external_auth loading RT user for $user\n"
317     if $DEBUG > 1;
318
319   $session->{'CurrentUser'}->Load($user);
320
321   if ( ! $session->{'CurrentUser'}->Id() ) {
322
323       # Create users on-the-fly
324
325       warn "can't load RT user for $user; auto-creating\n"
326         if $DEBUG;
327
328       my $UserObj = RT::User->new( RT::CurrentUser->new('RT_System') );
329
330       my ( $val, $msg ) = $UserObj->Create(
331           %{ ref($RT::AutoCreate) ? $RT::AutoCreate : {} },
332           Name  => $user,
333           Gecos => $user,
334       );
335
336       if ($val) {
337
338           # now get user specific information, to better create our user.
339           my $new_user_info
340               = RT::Interface::Web::WebExternalAutoInfo($user);
341
342           # set the attributes that have been defined.
343           # FIXME: this is a horrible kludge. I'm sure there's something cleaner
344           foreach my $attribute (
345               'Name',                  'Comments',
346               'Signature',             'EmailAddress',
347               'PagerEmailAddress',     'FreeformContactInfo',
348               'Organization',          'Disabled',
349               'Privileged',            'RealName',
350               'NickName',              'Lang',
351               'EmailEncoding',         'WebEncoding',
352               'ExternalContactInfoId', 'ContactInfoSystem',
353               'ExternalAuthId',        'Gecos',
354               'HomePhone',             'WorkPhone',
355               'MobilePhone',           'PagerPhone',
356               'Address1',              'Address2',
357               'City',                  'State',
358               'Zip',                   'Country'
359               )
360           {
361               #uhh, wrong root
362               #$m->comp( '/Elements/Callback', %ARGS,
363               #    _CallbackName => 'NewUser' );
364
365               my $method = "Set$attribute";
366               $UserObj->$method( $new_user_info->{$attribute} )
367                   if ( defined $new_user_info->{$attribute} );
368           }
369           $session->{'CurrentUser'}->Load($user);
370       }
371       else {
372
373          # we failed to successfully create the user. abort abort abort.
374           delete $session->{'CurrentUser'};
375
376           die "can't auto-create RT user"; #an error message would be nice :/
377           #$m->abort() unless $RT::WebFallbackToInternalAuth;
378           #$m->comp( '/Elements/Login', %ARGS,
379           #    Error => loc( 'Cannot create user: [_1]', $msg ) );
380       }
381   }
382
383   unless ( $session->{'CurrentUser'}->Id() ) {
384       delete $session->{'CurrentUser'};
385
386       die "can't auto-create RT user";
387       #$user = $orig_user;
388       # 
389       #if ($RT::WebExternalOnly) {
390       #    $m->comp( '/Elements/Login', %ARGS,
391       #        Error => loc('You are not an authorized user') );
392       #    $m->abort();
393       #}
394   }
395
396   $session;
397
398 }
399
400 1;
401