add "Omit TLD from domains" option, RT#16751
[freeside.git] / FS / FS / part_export / netsapiens.pm
1 package FS::part_export::netsapiens;
2
3 use vars qw(@ISA $me %info);
4 use MIME::Base64;
5 use Tie::IxHash;
6 use FS::part_export;
7 use Date::Format qw( time2str );
8
9 @ISA = qw(FS::part_export);
10 $me = '[FS::part_export::netsapiens]';
11
12 #These export options set default values for the various commands
13 #to create/update objects.  Add more options as needed.
14
15 my %tristate = ( type => 'select', options => [ '', 'yes', 'no' ]);
16
17 tie my %subscriber_fields, 'Tie::IxHash',
18   'admin_vmail'     => { label=>'VMail Prov.', %tristate },
19   'dial_plan'       => { label=>'Dial Translation' },
20   'dial_policy'     => { label=>'Dial Permission' },
21   'call_limit'      => { label=>'Call Limit' },
22   'domain_dir'      => { label=>'Dir Lst', %tristate },
23 ;
24
25 tie my %registrar_fields, 'Tie::IxHash',
26   'authenticate_register' => { label=>'Authenticate Registration', %tristate },
27   'authentication_realm'  => { label=>'Authentication Realm' },
28 ;
29
30 tie my %dialplan_fields, 'Tie::IxHash',
31   'responder'       => { label=>'Application' }, #this could be nicer
32   'from_name'       => { label=>'Source Name Translation' },
33   'from_user'       => { label=>'Source User Translation' },
34 ;
35
36 tie my %options, 'Tie::IxHash',
37   'login'           => { label=>'NetSapiens tac2 User API username' },
38   'password'        => { label=>'NetSapiens tac2 User API password' },
39   'url'             => { label=>'NetSapiens tac2 User URL' },
40   'device_login'    => { label=>'NetSapiens tac2 Device API username' },
41   'device_password' => { label=>'NetSapiens tac2 Device API password' },
42   'device_url'      => { label=>'NetSapiens tac2 Device URL' },
43   'domain'          => { label=>'NetSapiens Domain' },
44   'domain_no_tld'   => { label=>'Omit TLD from domains', type=>'checkbox' },
45   'debug'           => { label=>'Enable debugging', type=>'checkbox' },
46   %subscriber_fields,
47   %registrar_fields,
48   %dialplan_fields,
49   'did_countrycode' => { label=>'Use country code in DID destination',
50                          type =>'checkbox' },
51 ;
52
53 %info = (
54   'svc'      => [ 'svc_phone', ], # 'part_device',
55   'desc'     => 'Provision phone numbers to NetSapiens',
56   'options'  => \%options,
57   'notes'    => <<'END'
58 Requires installation of
59 <a href="http://search.cpan.org/dist/REST-Client">REST::Client</a>
60 from CPAN.
61 END
62 );
63
64 sub rebless { shift; }
65
66 sub ns_command {
67   my $self = shift;
68   $self->_ns_command('', @_);
69 }
70
71 sub ns_device_command { 
72   my $self = shift;
73   $self->_ns_command('device_', @_);
74 }
75
76 sub _ns_command {
77   my( $self, $prefix, $method, $command ) = splice(@_,0,4);
78
79   # kludge to curb excessive paranoia in LWP 6.0+
80   local $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
81   eval 'use REST::Client';
82   die $@ if $@;
83
84   my $ns = new REST::Client 'host'=>$self->option($prefix.'url');
85
86   my @args = ( $command );
87
88   if ( $method eq 'PUT' ) {
89     my $content = $ns->buildQuery( { @_ } );
90     $content =~ s/^\?//;
91     push @args, $content;
92   } elsif ( $method eq 'GET' ) {
93     $args[0] .= $ns->buildQuery( { @_ } );
94   }
95
96   warn "$me $method ". $self->option($prefix.'url'). join(', ', @args). "\n"
97     if $self->option('debug');
98
99   my $auth = encode_base64( $self->option($prefix.'login'). ':'.
100                             $self->option($prefix.'password')    );
101   push @args, { 'Authorization' => "Basic $auth" };
102
103   $ns->$method( @args );
104   $ns;
105 }
106
107 sub ns_domain {
108   my($self, $svc_phone) = (shift, shift);
109   my $domain = $svc_phone->domain || $self->option('domain');
110
111   $domain =~ s/\.\w{2,4}$//
112     if $self->option('domain_no_tld');
113   
114   $domain;
115 }
116
117 sub ns_subscriber {
118   my($self, $svc_phone) = (shift, shift);
119
120   my $domain = $self->ns_domain($svc_phone);
121   my $phonenum = $svc_phone->phonenum;
122
123   "/domains_config/$domain/subscriber_config/$phonenum";
124 }
125
126 sub ns_registrar {
127   my($self, $svc_phone) = (shift, shift);
128
129   $self->ns_subscriber($svc_phone).
130     '/registrar_config/'. $self->ns_devicename($svc_phone);
131 }
132
133 sub ns_devicename {
134   my( $self, $svc_phone ) = (shift, shift);
135
136   my $domain = $self->ns_domain($svc_phone);
137   #my $countrycode = $svc_phone->countrycode;
138   my $phonenum    = $svc_phone->phonenum;
139
140   #"sip:$countrycode$phonenum\@$domain";
141   "sip:$phonenum\@$domain";
142 }
143
144 sub ns_dialplan {
145   my($self, $svc_phone) = (shift, shift);
146
147   my $countrycode = $svc_phone->countrycode || '1';
148   my $phonenum    = $svc_phone->phonenum;
149   # Only in the dialplan destination, nowhere else
150   if ( $self->option('did_countrycode') ) {
151     $phonenum = $countrycode . $phonenum;
152   }
153
154   #"/dialplans/DID+Table/dialplan_config/sip:$countrycode$phonenum\@*"
155   "/domains_config/admin-only/dialplans/DID+Table/dialplan_config/sip:$phonenum\@*,*,*,*,*,*,*";
156 }
157
158 sub ns_device {
159   my($self, $svc_phone, $phone_device ) = (shift, shift, shift);
160
161   #my $countrycode = $svc_phone->countrycode;
162   #my $phonenum    = $svc_phone->phonenum;
163
164   "/phones_config/". lc($phone_device->mac_addr);
165 }
166
167 sub ns_create_or_update {
168   my($self, $svc_phone, $dial_policy) = (shift, shift, shift);
169
170   my $domain = $self->ns_domain($svc_phone);
171   #my $countrycode = $svc_phone->countrycode;
172   my $phonenum    = $svc_phone->phonenum;
173
174   #deal w/unaudited netsapiens services?
175   my $cust_main = $svc_phone->cust_svc->cust_pkg->cust_main;
176
177   my( $firstname, $lastname );
178   if ( $svc_phone->phone_name =~ /^\s*(\S+)\s+(\S.*\S)\s*$/ ) {
179     $firstname = $1;
180     $lastname  = $2;
181   } else {
182     $firstname = $cust_main->get('first');
183     $lastname  = $cust_main->get('last');
184   }
185
186   my ($email) = ($cust_main->invoicing_list_emailonly, '');
187   my $custnum = $cust_main->custnum;
188
189   # Piece 1 (already done) - User creation
190   
191   $phonenum =~ /^(\d{3})/;
192   my $area_code = $1;
193
194   my $ns = $self->ns_command( 'PUT', $self->ns_subscriber($svc_phone), 
195     'subscriber_login' => $phonenum.'@'.$domain,
196     'firstname'        => $firstname,
197     'lastname'         => $lastname,
198     'subscriber_pin'   => $svc_phone->pin,
199     'callid_name'      => "$firstname $lastname",
200     'callid_nmbr'      => $phonenum,
201     'callid_emgr'      => $phonenum,
202     'email_address'    => $email,
203     'area_code'        => $area_code,
204     'srv_code'         => $custnum,
205     'date_created'     => time2str('%Y-%m-%d %H:%M:%S', time),
206     $self->options_named(keys %subscriber_fields),
207     # allow this to be overridden for suspend
208     ( $dial_policy ? ('dial_policy' => $dial_policy) : () ),
209   );
210
211   if ( $ns->responseCode !~ /^2/ ) {
212      return $ns->responseCode. ' '.
213             join(', ', $self->ns_parse_response( $ns->responseContent ) );
214   }
215
216   #Piece 2 - sip device creation 
217
218   my $ns2 = $self->ns_command( 'PUT', $self->ns_registrar($svc_phone),
219     'termination_match' => $self->ns_devicename($svc_phone),
220     'authentication_key'=> $svc_phone->sip_password,
221     'srv_code'          => $custnum,
222     $self->options_named(keys %registrar_fields),
223   );
224
225   if ( $ns2->responseCode !~ /^2/ ) {
226      return $ns2->responseCode. ' '.
227             join(', ', $self->ns_parse_response( $ns2->responseContent ) );
228   }
229
230   #Piece 3 - DID mapping to user
231
232   my $ns3 = $self->ns_command( 'PUT', $self->ns_dialplan($svc_phone),
233     'to_user' => $phonenum,
234     'to_host' => $domain,
235     'plan_description' => "$custnum: $lastname, $firstname", #config?
236     $self->options_named(keys %dialplan_fields),
237   );
238
239   if ( $ns3->responseCode !~ /^2/ ) {
240      return $ns3->responseCode. ' '.
241             join(', ', $self->ns_parse_response( $ns3->responseContent ) );
242   }
243
244   '';
245 }
246
247 sub ns_delete {
248   my($self, $svc_phone) = (shift, shift);
249
250   # do the create steps in reverse order, though I'm not sure it matters
251
252   my $ns3 = $self->ns_command( 'DELETE', $self->ns_dialplan($svc_phone) );
253
254   if ( $ns3->responseCode !~ /^2/ ) {
255      return $ns3->responseCode. ' '.
256             join(', ', $self->ns_parse_response( $ns3->responseContent ) );
257   }
258
259   my $ns2 = $self->ns_command( 'DELETE', $self->ns_registrar($svc_phone) );
260
261   if ( $ns2->responseCode !~ /^2/ ) {
262      return $ns2->responseCode. ' '.
263             join(', ', $self->ns_parse_response( $ns2->responseContent ) );
264   }
265
266   my $ns = $self->ns_command( 'DELETE', $self->ns_subscriber($svc_phone) );
267
268   if ( $ns->responseCode !~ /^2/ ) {
269      return $ns->responseCode. ' '.
270             join(', ', $self->ns_parse_response( $ns->responseContent ) );
271   }
272
273   '';
274
275 }
276
277 sub ns_parse_response {
278   my( $self, $content ) = ( shift, shift );
279
280   #try to screen-scrape something useful
281   tie my %hash, Tie::IxHash;
282   while ( $content =~ s/^.*?<p>\s*<b>(.+?)<\/b>\s*(.+?)\s*<\/p>//is ) {
283     ( $hash{$1} = $2 ) =~ s/^\s*<(\w+)>(.+?)<\/\1>/$2/is;
284   }
285
286   %hash;
287 }
288
289 sub _export_insert {
290   my($self, $svc_phone) = (shift, shift);
291   $self->ns_create_or_update($svc_phone);
292 }
293
294 sub _export_replace {
295   my( $self, $new, $old ) = (shift, shift, shift);
296   return "can't change phonenum with NetSapiens (unprovision and reprovision?)"
297     if $old->phonenum ne $new->phonenum;
298   $self->_export_insert($new);
299 }
300
301 sub _export_delete {
302   my( $self, $svc_phone ) = (shift, shift);
303
304   $self->ns_delete($svc_phone);
305 }
306
307 sub _export_suspend {
308   my( $self, $svc_phone ) = (shift, shift);
309   $self->ns_create_or_update($svc_phone, 'Deny');
310 }
311
312 sub _export_unsuspend {
313   my( $self, $svc_phone ) = (shift, shift);
314   #$self->ns_create_or_update($svc_phone, 'Permit All');
315   $self->_export_insert($svc_phone);
316 }
317
318 sub export_device_insert {
319   my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
320
321   my $domain = $self->ns_domain($svc_phone);
322   my $countrycode = $svc_phone->countrycode;
323   my $phonenum    = $svc_phone->phonenum;
324
325   my $ns = $self->ns_device_command(
326     'PUT', $self->ns_device($svc_phone, $phone_device),
327       'line1_enable' => 'yes',
328       'device1'      => $self->ns_devicename($svc_phone),
329       'line1_ext'    => $phonenum,
330 ,
331       #'line2_enable' => 'yes',
332       #'device2'      =>
333       #'line2_ext'    =>
334
335       #'notes' => 
336       'server'       => 'SiPbx',
337       'domain'       => $domain,
338
339       'brand'        => $phone_device->part_device->devicename,
340       
341   );
342
343   if ( $ns->responseCode !~ /^2/ ) {
344      return $ns->responseCode. ' '.
345             join(', ', $self->ns_parse_response( $ns->responseContent ) );
346   }
347
348   '';
349
350 }
351
352 sub export_device_delete {
353   my( $self, $svc_phone, $phone_device ) = (shift, shift, shift);
354
355   my $ns = $self->ns_device_command(
356     'DELETE', $self->ns_device($svc_phone, $phone_device),
357   );
358
359   if ( $ns->responseCode !~ /^2/ ) {
360      return $ns->responseCode. ' '.
361             join(', ', $self->ns_parse_response( $ns->responseContent ) );
362   }
363
364   '';
365
366 }
367
368
369 sub export_device_replace {
370   my( $self, $svc_phone, $new_phone_device, $old_phone_device ) =
371     (shift, shift, shift, shift);
372
373   #?
374   $self->export_device_insert( $svc_phone, $new_phone_device );
375
376 }
377
378 sub export_links {
379   my($self, $svc_phone, $arrayref) = (shift, shift, shift);
380   #push @$arrayref, qq!<A HREF="http://example.com/~!. $svc_phone->username.
381   #                 qq!">!. $svc_phone->username. qq!</A>!;
382   '';
383 }
384
385 sub options_named {
386   my $self = shift;
387   map { 
388         my $v = $self->option($_);
389         length($v) ? ($_ => $v) : ()
390       } @_
391 }
392
393 1;