1 package FS::access_user;
4 use base qw( FS::m2m_Common FS::option_Common );
5 use vars qw( $DEBUG $me $conf );
9 use FS::Record qw( qsearch qsearchs dbh );
10 use FS::access_user_pref;
11 use FS::access_usergroup;
16 $me = '[FS::access_user]';
20 FS::access_user - Object methods for access_user records
26 $record = new FS::access_user \%hash;
27 $record = new FS::access_user { 'column' => 'value' };
29 $error = $record->insert;
31 $error = $new_record->replace($old_record);
33 $error = $record->delete;
35 $error = $record->check;
39 An FS::access_user object represents an internal access user. FS::access_user
40 inherits from FS::Record. The following fields are currently supported:
44 =item usernum - primary key
54 =item disabled - empty or 'Y'
64 Creates a new internal access user. To add the user to the database, see L<"insert">.
66 Note that this stores the hash reference, not a distinct copy of the hash it
67 points to. You can ask the object for a copy with the I<hash> method.
71 # the new method can be inherited from FS::Record, if a table method is defined
73 sub table { 'access_user'; }
75 sub _option_table { 'access_user_pref'; }
76 sub _option_namecol { 'prefname'; }
77 sub _option_valuecol { 'prefvalue'; }
81 Adds this record to the database. If there is an error, returns the error,
82 otherwise returns false.
89 my $error = $self->check;
90 return $error if $error;
92 local $SIG{HUP} = 'IGNORE';
93 local $SIG{INT} = 'IGNORE';
94 local $SIG{QUIT} = 'IGNORE';
95 local $SIG{TERM} = 'IGNORE';
96 local $SIG{TSTP} = 'IGNORE';
97 local $SIG{PIPE} = 'IGNORE';
99 my $oldAutoCommit = $FS::UID::AutoCommit;
100 local $FS::UID::AutoCommit = 0;
104 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
108 $error = $self->SUPER::insert(@_);
111 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
114 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
122 Delete this record from the database.
129 local $SIG{HUP} = 'IGNORE';
130 local $SIG{INT} = 'IGNORE';
131 local $SIG{QUIT} = 'IGNORE';
132 local $SIG{TERM} = 'IGNORE';
133 local $SIG{TSTP} = 'IGNORE';
134 local $SIG{PIPE} = 'IGNORE';
136 my $oldAutoCommit = $FS::UID::AutoCommit;
137 local $FS::UID::AutoCommit = 0;
140 my $error = $self->SUPER::delete(@_);
143 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
146 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
152 =item replace OLD_RECORD
154 Replaces the OLD_RECORD with this one in the database. If there is an error,
155 returns the error, otherwise returns false.
162 my $old = ( ref($_[0]) eq ref($new) )
166 local $SIG{HUP} = 'IGNORE';
167 local $SIG{INT} = 'IGNORE';
168 local $SIG{QUIT} = 'IGNORE';
169 local $SIG{TERM} = 'IGNORE';
170 local $SIG{TSTP} = 'IGNORE';
171 local $SIG{PIPE} = 'IGNORE';
173 my $oldAutoCommit = $FS::UID::AutoCommit;
174 local $FS::UID::AutoCommit = 0;
177 return "Must change password when enabling this account"
178 if $old->disabled && !$new->disabled
179 && ( $new->_password =~ /changeme/i
180 || $new->_password eq 'notyet'
183 my $error = $new->SUPER::replace($old, @_);
186 $dbh->rollback or die $dbh->errstr if $oldAutoCommit;
189 $dbh->commit or die $dbh->errstr if $oldAutoCommit;
197 Checks all fields to make sure this is a valid internal access user. If there is
198 an error, returns the error, otherwise returns false. Called by the insert
203 # the check method should currently be supplied - FS::Record contains some
204 # data checking routines
210 $self->ut_numbern('usernum')
211 || $self->ut_alpha_lower('username')
212 || $self->ut_textn('_password')
213 || $self->ut_textn('last')
214 || $self->ut_textn('first')
215 || $self->ut_foreign_keyn('user_custnum', 'cust_main', 'custnum')
216 || $self->ut_enum('disabled', [ '', 'Y' ] )
218 return $error if $error;
225 Returns a name string for this user: "Last, First".
231 return $self->username
232 if $self->get('last') eq 'Lastname' && $self->first eq 'Firstname'
233 or $self->get('last') eq '' && $self->first eq '';
234 return $self->get('last'). ', '. $self->first;
239 Returns the FS::cust_main object (see L<FS::cust_main>), if any, for this
246 qsearchs( 'cust_main', { 'custnum' => $self->user_custnum } );
249 =item access_usergroup
251 Returns links to the the groups this user is a part of, as FS::access_usergroup
252 objects (see L<FS::access_usergroup>).
256 sub access_usergroup {
258 qsearch( 'access_usergroup', { 'usernum' => $self->usernum } );
269 #=item access_groupnames
273 #sub access_groupnames {
279 Returns a list of agentnums this user can view (via group membership).
285 my $sth = dbh->prepare(
286 "SELECT DISTINCT agentnum FROM access_usergroup
287 JOIN access_groupagent USING ( groupnum )
289 ) or die dbh->errstr;
290 $sth->execute($self->usernum) or die $sth->errstr;
291 map { $_->[0] } @{ $sth->fetchall_arrayref };
296 Returns a hashref of agentnums this user can view.
302 scalar( { map { $_ => 1 } $self->agentnums } );
305 =item agentnums_sql [ HASHREF | OPTION => VALUE ... ]
307 Returns an sql fragement to select only agentnums this user can view.
309 Options are passed as a hashref or a list. Available options are:
315 The frament will also allow the selection of null agentnums.
319 The fragment will also allow the selection of null agentnums if the current
320 user has the provided access right
324 Optional table name in which agentnum is being checked. Sometimes required to
325 resolve 'column reference "agentnum" is ambiguous' errors.
329 All agents will be viewable if the current user has the provided access right.
330 Defaults to 'View customers of all agents'.
338 my %opt = ref($_[0]) ? %{$_[0]} : @_;
340 my $agentnum = $opt{'table'} ? $opt{'table'}.'.agentnum' : 'agentnum';
344 my $viewall_right = $opt{'viewall_right'} || 'View customers of all agents';
345 if ( $self->access_right($viewall_right) ) {
346 push @or, "$agentnum IS NOT NULL";
348 push @or, "$agentnum IN (". join(',', $self->agentnums). ')';
351 push @or, "$agentnum IS NULL"
353 || ( $opt{'null_right'} && $self->access_right($opt{'null_right'}) );
355 return ' 1 = 0 ' unless scalar(@or);
356 '( '. join( ' OR ', @or ). ' )';
362 Returns true if the user can view the specified agent.
367 my( $self, $agentnum ) = @_;
368 my $sth = dbh->prepare(
369 "SELECT COUNT(*) FROM access_usergroup
370 JOIN access_groupagent USING ( groupnum )
371 WHERE usernum = ? AND agentnum = ?"
372 ) or die dbh->errstr;
373 $sth->execute($self->usernum, $agentnum) or die $sth->errstr;
374 $sth->fetchrow_arrayref->[0];
377 =item agents [ HASHREF | OPTION => VALUE ... ]
379 Returns the list of agents this user can view (via group membership), as
380 FS::agent objects. Accepts the same options as the agentnums_sql method.
388 'hashref' => { disabled=>'' },
389 'extra_sql' => ' AND '. $self->agentnums_sql(@_),
393 =item access_right RIGHTNAME | LISTREF
395 Given a right name or a list reference of right names, returns true if this
396 user has this right, or, for a list, one of the rights (currently via group
397 membership, eventually also via user overrides).
402 my( $self, $rightname ) = @_;
404 $rightname = [ $rightname ] unless ref($rightname);
406 warn "$me access_right called on ". join(', ', @$rightname). "\n"
409 #some caching of ACL requests for low-hanging fruit perf improvement
410 #since we get a new $CurrentUser object each page view there shouldn't be any
411 #issues with stickiness
412 if ( $self->{_ACLcache} ) {
414 unless ( grep !exists($self->{_ACLcache}{$_}), @$rightname ) {
415 warn "$me ACL cache hit for ". join(', ', @$rightname). "\n"
417 return grep $self->{_ACLcache}{$_}, @$rightname
420 warn "$me ACL cache miss for ". join(', ', @$rightname). "\n"
425 warn "initializing ACL cache\n"
427 $self->{_ACLcache} = {};
431 my $has_right = ' rightname IN ('. join(',', map '?', @$rightname ). ') ';
433 my $sth = dbh->prepare("
434 SELECT groupnum FROM access_usergroup
435 LEFT JOIN access_group USING ( groupnum )
436 LEFT JOIN access_right
437 ON ( access_group.groupnum = access_right.rightobjnum )
439 AND righttype = 'FS::access_group'
442 ") or die dbh->errstr;
443 $sth->execute($self->usernum, @$rightname) or die $sth->errstr;
444 my $row = $sth->fetchrow_arrayref;
446 my $return = $row ? $row->[0] : '';
448 #just caching the single-rightname hits should be enough of a win for now
449 if ( scalar(@$rightname) == 1 ) {
450 $self->{_ACLcache}{${$rightname}[0]} = $return;
457 =item default_customer_view
459 Returns the default customer view for this user, from the
460 "default_customer_view" user preference, the "cust_main-default_view" config,
461 or the hardcoded default, "basics" (formerly "jumbo" prior to 3.0).
465 sub default_customer_view {
468 $self->option('default_customer_view')
469 || $conf->config('cust_main-default_view')
470 || 'basics'; #s/jumbo/basics/ starting with 3.0
474 =item spreadsheet_format [ OVERRIDE ]
476 Returns a hashref of this user's Excel spreadsheet download settings:
477 'extension' (xls or xlsx), 'class' (Spreadsheet::WriteExcel or
478 Excel::Writer::XLSX), and 'mime_type'. If OVERRIDE is 'XLS' or 'XLSX',
479 use that instead of the user's setting.
483 # is there a better place to put this?
487 class => 'Spreadsheet::WriteExcel',
488 mime_type => 'application/vnd.ms-excel',
491 extension => '.xlsx',
492 class => 'Excel::Writer::XLSX',
493 mime_type => # it's on wikipedia, it must be true
494 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
498 sub spreadsheet_format {
500 my $override = shift;
503 || $self->option('spreadsheet_format')
504 || $conf->config('spreadsheet_format')
512 Returns true if this user has the name of a known system account. These
513 users cannot log into the web interface and can't have passwords set.
519 return grep { $_ eq $self->username } ( qw(
529 =item change_password NEW_PASSWORD
533 sub change_password {
534 #my( $self, $password ) = @_;
535 #FS::Auth->auth_class->change_password( $self, $password );
536 FS::Auth->auth_class->change_password( @_ );
539 =item change_password_fields NEW_PASSWORD
543 sub change_password_fields {
544 #my( $self, $password ) = @_;
545 #FS::Auth->auth_class->change_password_fields( $self, $password );
546 FS::Auth->auth_class->change_password_fields( @_ );
555 L<FS::Record>, schema.html from the base documentation.