Merge branch 'xss_fixes' of https://github.com/mcreenan/Freeside
[freeside.git] / FS / FS / svc_pbx.pm
1 package FS::svc_pbx;
2
3 use strict;
4 use base qw( FS::svc_External_Common );
5 use FS::Record qw( qsearch qsearchs dbh );
6 use FS::PagedSearch qw( psearch );
7 use FS::Conf;
8 use FS::cust_svc;
9 use FS::svc_phone;
10 use FS::svc_acct;
11
12 =head1 NAME
13
14 FS::svc_pbx - Object methods for svc_pbx records
15
16 =head1 SYNOPSIS
17
18   use FS::svc_pbx;
19
20   $record = new FS::svc_pbx \%hash;
21   $record = new FS::svc_pbx { 'column' => 'value' };
22
23   $error = $record->insert;
24
25   $error = $new_record->replace($old_record);
26
27   $error = $record->delete;
28
29   $error = $record->check;
30
31   $error = $record->suspend;
32
33   $error = $record->unsuspend;
34
35   $error = $record->cancel;
36
37 =head1 DESCRIPTION
38
39 An FS::svc_pbx object represents a PBX tenant.  FS::svc_pbx inherits from
40 FS::svc_Common.  The following fields are currently supported:
41
42 =over 4
43
44 =item svcnum
45
46 Primary key (assigned automatcially for new accounts)
47
48 =item id
49
50 (Unique?) number of external record
51
52 =item title
53
54 PBX name
55
56 =item max_extensions
57
58 Maximum number of extensions
59
60 =item max_simultaneous
61
62 Maximum number of simultaneous users
63
64 =back
65
66 =head1 METHODS
67
68 =over 4
69
70 =item new HASHREF
71
72 Creates a new PBX tenant.  To add the PBX tenant to the database, see
73 L<"insert">.
74
75 Note that this stores the hash reference, not a distinct copy of the hash it
76 points to.  You can ask the object for a copy with the I<hash> method.
77
78 =cut
79
80 sub table { 'svc_pbx'; }
81
82 sub table_info {
83   {
84     'name' => 'PBX',
85     'name_plural' => 'PBXs',
86     'lcname_plural' => 'PBXs',
87     'longname_plural' => 'PBXs',
88     'sorts' => 'svcnum', # optional sort field (or arrayref of sort fields, main first)
89     'display_weight' => 70,
90     'cancel_weight'  => 90,
91     'fields' => {
92       'id'    => 'ID',
93       'title' => 'Name',
94       'max_extensions' => 'Maximum number of User Extensions',
95       'max_simultaneous' => 'Maximum number of simultaneous users',
96     },
97   };
98 }
99
100 =item search_sql STRING
101
102 Class method which returns an SQL fragment to search for the given string.
103
104 =cut
105
106 #XXX
107 #or something more complicated if necessary
108 #sub search_sql {
109 #  my($class, $string) = @_;
110 #  $class->search_sql_field('title', $string);
111 #}
112
113 =item label
114
115 Returns the title field for this PBX tenant.
116
117 =cut
118
119 sub label {
120   my $self = shift;
121   $self->title;
122 }
123
124 =item insert
125
126 Adds this record to the database.  If there is an error, returns the error,
127 otherwise returns false.
128
129 The additional fields pkgnum and svcpart (see L<FS::cust_svc>) should be 
130 defined.  An FS::cust_svc record will be created and inserted.
131
132 =cut
133
134 sub insert {
135   my $self = shift;
136   my $error;
137
138   $error = $self->SUPER::insert;
139   return $error if $error;
140
141   '';
142 }
143
144 =item delete
145
146 Delete this record from the database.
147
148 =cut
149
150 sub delete {
151   my $self = shift;
152
153   local $SIG{HUP} = 'IGNORE';
154   local $SIG{INT} = 'IGNORE';
155   local $SIG{QUIT} = 'IGNORE';
156   local $SIG{TERM} = 'IGNORE';
157   local $SIG{TSTP} = 'IGNORE';
158   local $SIG{PIPE} = 'IGNORE';
159
160   my $oldAutoCommit = $FS::UID::AutoCommit;
161   local $FS::UID::AutoCommit = 0;
162   my $dbh = dbh;
163
164   foreach my $svc_phone (qsearch('svc_phone', { 'pbxsvc' => $self->svcnum } )) {
165     $svc_phone->pbxsvc('');
166     my $error = $svc_phone->replace;
167     if ( $error ) {
168       $dbh->rollback if $oldAutoCommit;
169       return $error;
170     }
171   }
172
173   foreach my $svc_acct  (qsearch('svc_acct',  { 'pbxsvc' => $self->svcnum } )) {
174     my $error = $svc_acct->delete;
175     if ( $error ) {
176       $dbh->rollback if $oldAutoCommit;
177       return $error;
178     }
179   }
180
181   my $error = $self->SUPER::delete;
182   if ( $error ) {
183     $dbh->rollback if $oldAutoCommit;
184     return $error;
185   }
186
187   $dbh->commit or die $dbh->errstr if $oldAutoCommit;
188   '';
189 }
190
191
192 =item replace OLD_RECORD
193
194 Replaces the OLD_RECORD with this one in the database.  If there is an error,
195 returns the error, otherwise returns false.
196
197 =cut
198
199 #sub replace {
200 #  my ( $new, $old ) = ( shift, shift );
201 #  my $error;
202 #
203 #  $error = $new->SUPER::replace($old);
204 #  return $error if $error;
205 #
206 #  '';
207 #}
208
209 =item suspend
210
211 Called by the suspend method of FS::cust_pkg (see L<FS::cust_pkg>).
212
213 =item unsuspend
214
215 Called by the unsuspend method of FS::cust_pkg (see L<FS::cust_pkg>).
216
217 =item cancel
218
219 Called by the cancel method of FS::cust_pkg (see L<FS::cust_pkg>).
220
221 =item check
222
223 Checks all fields to make sure this is a valid PBX tenant.  If there is
224 an error, returns the error, otherwise returns false.  Called by the insert
225 and repalce methods.
226
227 =cut
228
229 sub check {
230   my $self = shift;
231
232   my $x = $self->setfixed;
233   return $x unless ref($x);
234   my $part_svc = $x;
235
236
237   $self->SUPER::check;
238 }
239
240 sub _check_duplicate {
241   my $self = shift;
242
243   my $conf = new FS::Conf;
244   
245   $self->lock_table;
246
247   foreach my $field ('title', 'id') {
248     my $global_unique = $conf->config("global_unique-pbx_$field");
249     # can be 'disabled', 'enabled', or empty.
250     # if empty, check per exports; if not empty or disabled, check 
251     # globally.
252     next if $global_unique eq 'disabled';
253     my @dup = $self->find_duplicates(
254       ($global_unique ? 'global' : 'export') , $field
255     );
256     next if !@dup;
257     return "duplicate $field '".$self->getfield($field).
258            "': conflicts with svcnum ".$dup[0]->svcnum;
259   }
260   return '';
261 }
262
263 =item psearch_cdrs OPTIONS
264
265 Returns a paged search (L<FS::PagedSearch>) for Call Detail Records 
266 associated with this service.  By default, "associated with" means that 
267 the "charged_party" field of the CDR matches the "title" field of the 
268 service.  To access the CDRs themselves, call "->fetch" on the resulting
269 object.
270
271 =over 2
272
273 Accepts the following options:
274
275 =item for_update => 1: SELECT the CDRs "FOR UPDATE".
276
277 =item status => "" (or "done"): Return only CDRs with that processing status.
278
279 =item inbound => 1: No-op for svc_pbx CDR processing.
280
281 =item default_prefix => "XXX": Also accept the phone number of the service prepended 
282 with the chosen prefix.
283
284 =item disable_src => 1: No-op for svc_pbx CDR processing.
285
286 =item by_svcnum => 1: Select CDRs where the svcnum field matches, instead of 
287 title/charged_party.  Normally this field is set after processing.
288
289 =item by_ip_addr => 'src' or 'dst': Select CDRs where the src_ip_addr or 
290 dst_ip_addr field matches title.  In this case, some special logic is applied
291 to allow title to indicate a range of IP addresses.
292
293 =item begin, end: Start and end of date range, as unix timestamp.
294
295 =item cdrtypenum: Only return CDRs with this type.
296
297 =item calltypenum: Only return CDRs with this call type.
298
299 =back
300
301 =cut
302
303 sub psearch_cdrs {
304   my($self, %options) = @_;
305   my %hash = ();
306   my @where = ();
307
308   my @fields = ( 'charged_party' );
309   $hash{'freesidestatus'} = $options{'status'}
310     if exists($options{'status'});
311
312   if ($options{'cdrtypenum'}) {
313     $hash{'cdrtypenum'} = $options{'cdrtypenum'};
314   }
315   if ($options{'calltypenum'}) {
316     $hash{'calltypenum'} = $options{'calltypenum'};
317   }
318
319   my $for_update = $options{'for_update'} ? 'FOR UPDATE' : '';
320
321   if ( $options{'by_svcnum'} ) {
322     $hash{'svcnum'} = $self->svcnum;
323   }
324   elsif ( $options{'by_ip_addr'} =~ /^src|dst$/) {
325     my $field = 'cdr.'.$options{'by_ip_addr'}.'_ip_addr';
326     push @where, FS::cdr->ip_addr_sql($field, $self->title);
327   }
328   else {
329     #matching by title
330     my $title = $self->title;
331
332     my $prefix = $options{'default_prefix'};
333
334     my @orwhere =  map " $_ = '$title'        ", @fields;
335     push @orwhere, map " $_ = '$prefix$title' ", @fields
336       if length($prefix);
337     if ( $prefix =~ /^\+(\d+)$/ ) {
338       push @orwhere, map " $_ = '$1$title' ", @fields
339     }
340
341     push @where, ' ( '. join(' OR ', @orwhere ). ' ) ';
342   }
343
344   if ( $options{'begin'} ) {
345     push @where, 'startdate >= '. $options{'begin'};
346   }
347   if ( $options{'end'} ) {
348     push @where, 'startdate < '.  $options{'end'};
349   }
350
351   my $extra_sql = ( keys(%hash) ? ' AND ' : ' WHERE ' ). join(' AND ', @where )
352     if @where;
353
354   psearch( {
355       'table'      => 'cdr',
356       'hashref'    => \%hash,
357       'extra_sql'  => $extra_sql,
358       'order_by'   => "ORDER BY startdate $for_update",
359   } );
360 }
361
362 =item get_cdrs (DEPRECATED)
363
364 Like psearch_cdrs, but returns all the L<FS::cdr> objects at once, in a 
365 single list.  Arguments are the same as for psearch_cdrs.  This can take
366 an unreasonably large amount of memory and is best avoided.
367
368 =cut
369
370 sub get_cdrs {
371   my $self = shift;
372   my $psearch = $self->psearch_cdrs($_);
373   qsearch ( $psearch->{query} )
374 }
375
376 =back
377
378 =head1 BUGS
379
380 =head1 SEE ALSO
381
382 L<FS::svc_Common>, L<FS::Record>, L<FS::cust_svc>, L<FS::part_svc>,
383 L<FS::cust_pkg>, schema.html from the base documentation.
384
385 =cut
386
387 1;
388